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

Fixes https://github.com/elastic/kibana/issues/148067

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2023-01-09 16:17:15 -05:00 committed by GitHub
parent 0590127e16
commit 3b42548aeb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 86 additions and 111 deletions

View file

@ -12,11 +12,6 @@ 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';
@ -24,7 +19,6 @@ import {
getCoreChrome,
getCoreI18n,
getMapsCapabilities,
getToasts,
getEmbeddableService,
getDocLinks,
getCore,
@ -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) {
@ -81,11 +74,6 @@ export async function renderApp(
}
) {
goToSpecifiedPath = (path) => history.push(path);
kbnUrlStateStorage = createKbnUrlStateStorage({
useHash: false,
history,
...withNotifyOnErrors(getToasts()),
});
const stateTransfer = getEmbeddableService().getStateTransfer();

View file

@ -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,
@ -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';
@ -44,7 +48,6 @@ import { getMapEmbeddableDisplayName } from '../../../../common/i18n_getters';
import {
getInitialQuery,
getInitialRefreshConfig,
getInitialTimeFilters,
SavedMap,
unsavedChangesTitle,
unsavedChangesWarning,
@ -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);
@ -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() {
@ -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
);
@ -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;
@ -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() || [];
@ -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(
@ -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) => {

View file

@ -1,27 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public';
import { getUiSettings } from '../../../kibana_services';
import { SerializedMapState } from './types';
export function getInitialTimeFilters({
hasSaveAndReturnConfig,
serializedMapState,
globalState,
}: {
hasSaveAndReturnConfig: boolean;
serializedMapState?: SerializedMapState;
globalState: GlobalQueryStateFromUrl;
}) {
if (!hasSaveAndReturnConfig && serializedMapState?.timeFilters) {
return serializedMapState.timeFilters;
}
const defaultTime = getUiSettings().get('timepicker:timeDefaults');
return { ...defaultTime, ...globalState.time };
}

View file

@ -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';

View file

@ -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();

View file

@ -1,38 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { Filter, TimeRange } from '@kbn/es-query';
import { RefreshInterval } from '@kbn/data-plugin/public';
import { syncQueryStateWithUrl } from '@kbn/data-plugin/public';
import { getData } from '../../../kibana_services';
import { kbnUrlStateStorage } from '../../../render_app';
export interface MapsGlobalState {
time?: TimeRange;
refreshInterval?: RefreshInterval;
filters?: Filter[];
}
export function startGlobalStateSyncing() {
const { stop } = syncQueryStateWithUrl(getData().query, kbnUrlStateStorage);
return stop;
}
export function getGlobalState(): MapsGlobalState {
return kbnUrlStateStorage.get('_g') as MapsGlobalState;
}
export function updateGlobalState(newState: MapsGlobalState, flushUrlState = false) {
const globalState = getGlobalState();
kbnUrlStateStorage.set('_g', {
...globalState,
...newState,
});
if (flushUrlState) {
kbnUrlStateStorage.kbnUrlControls.flush(true);
}
}

View file

@ -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';

View file

@ -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();
@ -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');