Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[maps] fix Maps not initialising time range from URL #148465

Merged
merged 10 commits into from Jan 9, 2023
12 changes: 0 additions & 12 deletions x-pack/plugins/maps/public/render_app.tsx
Expand Up @@ -12,19 +12,13 @@ import { i18n } from '@kbn/i18n';
import type { CoreStart, AppMountParameters } from '@kbn/core/public';
import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-full-screen';
import { KibanaThemeProvider, toMountPoint } from '@kbn/kibana-react-plugin/public';
import {
createKbnUrlStateStorage,
withNotifyOnErrors,
IKbnUrlStateStorage,
} from '@kbn/kibana-utils-plugin/public';
import { FormattedRelative } from '@kbn/i18n-react';
import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
import { TableListViewKibanaProvider } from '@kbn/content-management-table-list';
import {
getCoreChrome,
getCoreI18n,
getMapsCapabilities,
getToasts,
getEmbeddableService,
getDocLinks,
getCore,
Expand All @@ -35,7 +29,6 @@ import { APP_ID } from '../common/constants';
import { registerLayerWizards } from './classes/layers/wizards/load_layer_wizards';

export let goToSpecifiedPath: (path: string) => void;
export let kbnUrlStateStorage: IKbnUrlStateStorage;

function setAppChrome() {
if (!getMapsCapabilities().save) {
Expand Down Expand Up @@ -81,11 +74,6 @@ export async function renderApp(
}
) {
goToSpecifiedPath = (path) => history.push(path);
kbnUrlStateStorage = createKbnUrlStateStorage({
useHash: false,
history,
...withNotifyOnErrors(getToasts()),
});

const stateTransfer = getEmbeddableService().getStateTransfer();

Expand Down
91 changes: 63 additions & 28 deletions x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx
Expand Up @@ -15,7 +15,18 @@ import { Subscription } from 'rxjs';
import { type Filter, FilterStateStore, type Query, type TimeRange } from '@kbn/es-query';
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
import type { DataView } from '@kbn/data-plugin/common';
import { SavedQuery, QueryStateChange, QueryState } from '@kbn/data-plugin/public';
import {
GlobalQueryStateFromUrl,
QueryState,
QueryStateChange,
SavedQuery,
syncGlobalQueryStateWithUrl,
} from '@kbn/data-plugin/public';
import {
createKbnUrlStateStorage,
withNotifyOnErrors,
IKbnUrlStateStorage,
} from '@kbn/kibana-utils-plugin/public';
import {
getData,
getExecutionContext,
Expand All @@ -27,14 +38,7 @@ import {
getTimeFilter,
getToasts,
} from '../../../kibana_services';
import {
AppStateManager,
startAppStateSyncing,
getGlobalState,
updateGlobalState,
startGlobalStateSyncing,
MapsGlobalState,
} from '../url_state';
import { AppStateManager, startAppStateSyncing } from '../url_state';
import { MapContainer } from '../../../connected_components/map_container';
import { getIndexPatternsFromIds } from '../../../index_pattern_util';
import { getTopNavConfig } from '../top_nav_config';
Expand All @@ -44,7 +48,6 @@ import { getMapEmbeddableDisplayName } from '../../../../common/i18n_getters';
import {
getInitialQuery,
getInitialRefreshConfig,
getInitialTimeFilters,
SavedMap,
unsavedChangesTitle,
unsavedChangesWarning,
Expand Down Expand Up @@ -100,6 +103,8 @@ export class MapApp extends React.Component<Props, State> {
_appStateManager = new AppStateManager();
_prevIndexPatternIds: string[] | null = null;
_isMounted: boolean = false;
_kbnUrlStateStorage: IKbnUrlStateStorage;
_initialTimeFromUrl: TimeRange | undefined;

constructor(props: Props) {
super(props);
Expand All @@ -109,6 +114,11 @@ export class MapApp extends React.Component<Props, State> {
isRefreshPaused: true,
refreshInterval: 0,
};
this._kbnUrlStateStorage = createKbnUrlStateStorage({
useHash: false,
history: props.history,
...withNotifyOnErrors(getToasts()),
});
}

componentDidMount() {
Expand All @@ -132,8 +142,16 @@ export class MapApp extends React.Component<Props, State> {
)
.subscribe();

this._globalSyncUnsubscribe = startGlobalStateSyncing();
this._appSyncUnsubscribe = startAppStateSyncing(this._appStateManager);
// syncGlobalQueryStateWithUrl mutates global state by merging URL state with Kibana QueryStart state
// capture _initialTimeFromUrl before global state is mutated
this._initialTimeFromUrl = this._getGlobalState()?.time;
const { stop } = syncGlobalQueryStateWithUrl(getData().query, this._kbnUrlStateStorage);
this._globalSyncUnsubscribe = stop;

this._appSyncUnsubscribe = startAppStateSyncing(
this._appStateManager,
this._kbnUrlStateStorage
);
this._globalSyncChangeMonitorSubscription = getData().query.state$.subscribe(
this._updateFromGlobalState
);
Expand Down Expand Up @@ -193,6 +211,20 @@ export class MapApp extends React.Component<Props, State> {
this._onQueryChange({ time: globalState.time });
};

_getGlobalState() {
return this._kbnUrlStateStorage.get<GlobalQueryStateFromUrl>('_g') ?? {};
}

_updateGlobalState(newState: GlobalQueryStateFromUrl) {
this._kbnUrlStateStorage.set('_g', {
...this._getGlobalState(),
...newState,
});
if (!this.state.initialized) {
this._kbnUrlStateStorage.kbnUrlControls.flush(true);
}
}

async _updateIndexPatterns() {
const { nextIndexPatternIds } = this.props;

Expand Down Expand Up @@ -247,17 +279,27 @@ export class MapApp extends React.Component<Props, State> {
});

// sync globalState
const updatedGlobalState: MapsGlobalState = {
const updatedGlobalState: GlobalQueryStateFromUrl = {
filters: filterManager.getGlobalFilters(),
};
if (time) {
updatedGlobalState.time = time;
}
updateGlobalState(updatedGlobalState, !this.state.initialized);
this._updateGlobalState(updatedGlobalState);
};

_getInitialTime(serializedMapState?: SerializedMapState) {
if (this._initialTimeFromUrl) {
return this._initialTimeFromUrl;
}

return !this.props.savedMap.hasSaveAndReturnConfig() && serializedMapState?.timeFilters
? serializedMapState.timeFilters
: getTimeFilter().getTime();
}

_initMapAndLayerSettings(serializedMapState?: SerializedMapState) {
const globalState: MapsGlobalState = getGlobalState();
const globalState = this._getGlobalState();

const savedObjectFilters = serializedMapState?.filters ? serializedMapState.filters : [];
const appFilters = this._appStateManager.getFilters() || [];
Expand All @@ -273,11 +315,7 @@ export class MapApp extends React.Component<Props, State> {
this._onQueryChange({
filters: [..._.get(globalState, 'filters', []), ...appFilters, ...savedObjectFilters],
query,
time: getInitialTimeFilters({
hasSaveAndReturnConfig: this.props.savedMap.hasSaveAndReturnConfig(),
serializedMapState,
globalState,
}),
time: this._getInitialTime(serializedMapState),
});

this._onRefreshConfigChange(
Expand All @@ -299,15 +337,12 @@ export class MapApp extends React.Component<Props, State> {
isRefreshPaused: isPaused,
refreshInterval: interval,
});
updateGlobalState(
{
refreshInterval: {
pause: isPaused,
value: interval,
},
this._updateGlobalState({
refreshInterval: {
pause: isPaused,
value: interval,
},
!this.state.initialized
);
});
}

_updateStateFromSavedQuery = (savedQuery: SavedQuery) => {
Expand Down

This file was deleted.

Expand Up @@ -10,6 +10,5 @@ export { SavedMap } from './saved_map';
export { getInitialLayersFromUrlParam } from './get_initial_layers_from_url_param';
export { getInitialQuery } from './get_initial_query';
export { getInitialRefreshConfig } from './get_initial_refresh_config';
export { getInitialTimeFilters } from './get_initial_time_filters';
export { unsavedChangesTitle, unsavedChangesWarning } from './get_breadcrumbs';
export { getOpenLayerWizardFromUrlParam } from './get_open_layer_wizard_url_param';
Expand Up @@ -8,12 +8,18 @@
import { map } from 'rxjs/operators';
import { FilterStateStore } from '@kbn/es-query';
import { connectToQueryState } from '@kbn/data-plugin/public';
import { syncState, BaseStateContainer } from '@kbn/kibana-utils-plugin/public';
import {
IKbnUrlStateStorage,
syncState,
BaseStateContainer,
} from '@kbn/kibana-utils-plugin/public';
import { getData } from '../../../kibana_services';
import { kbnUrlStateStorage } from '../../../render_app';
import { AppStateManager } from './app_state_manager';

export function startAppStateSyncing(appStateManager: AppStateManager) {
export function startAppStateSyncing(
appStateManager: AppStateManager,
kbnUrlStateStorage: IKbnUrlStateStorage
) {
// get appStateContainer
// sync app filters with app state container from data.query to state container
const { query } = getData();
Expand Down

This file was deleted.

Expand Up @@ -5,8 +5,6 @@
* 2.0.
*/

export type { MapsGlobalState } from './global_sync';
export { getGlobalState, updateGlobalState, startGlobalStateSyncing } from './global_sync';
export type { MapsAppState } from './app_state_manager';
export { AppStateManager } from './app_state_manager';
export { startAppStateSyncing } from './app_sync';
Expand Up @@ -21,13 +21,16 @@ export default function ({ getPageObjects, getService }) {
const MAP2_NAME = `${MAP_NAME_PREFIX}map2`;

describe('read', () => {
let joinMapUrl = '';
before(async () => {
await security.testUser.setRoles([
'global_maps_all',
'geoshape_data_reader',
'meta_for_geoshape_data_reader',
'test_logstash_reader',
]);
await PageObjects.maps.loadSavedMap('join example');
joinMapUrl = await browser.getCurrentUrl();
});
after(async () => {
await security.testUser.restoreDefaults();
Expand Down Expand Up @@ -58,6 +61,17 @@ export default function ({ getPageObjects, getService }) {
expect(layerExists).to.equal(true);
});

it('should override time stored with map when query is provided in global state', async () => {
const urlSplit = joinMapUrl.split('?');
const globalState = `_g=(time:(from:now-36m,to:now))`;
await browser.get(`${urlSplit[0]}?${globalState}`, true);
await PageObjects.maps.waitForLayersToLoad();

const timeConfig = await PageObjects.timePicker.getTimeConfig();
expect(timeConfig.start).to.equal('~ 36 minutes ago');
expect(timeConfig.end).to.equal('now');
});

describe('mapState contains query', () => {
before(async () => {
await PageObjects.maps.loadSavedMap('document example with query');
Expand Down