Skip to content

Commit

Permalink
move filters syncing utility to data plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Dosant committed Jan 22, 2020
1 parent c567e32 commit b60cad0
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ import {
import { initDashboardApp } from './legacy_app';
import { IEmbeddableStart } from '../../../../../../plugins/embeddable/public';
import { NavigationPublicPluginStart as NavigationStart } from '../../../../../../plugins/navigation/public';
import { DataPublicPluginStart as NpDataStart } from '../../../../../../plugins/data/public';
import {
DataPublicPluginStart as NpDataStart,
syncFilters,
} from '../../../../../../plugins/data/public';
import { SharePluginStart } from '../../../../../../plugins/share/public';
import { initGlobalState } from './kbn_global_state';

export interface RenderDeps {
core: LegacyCoreStart;
Expand Down Expand Up @@ -83,15 +85,15 @@ let angularModuleInstance: IModule | null = null;
const hasInheritedGlobalStateRef: { value: boolean } = { value: false };

export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => {
const { destroy: destroyGlobalState, hasInheritedGlobalState } = initGlobalState(
const { stop: stopSyncingGlobalFilters, hasInheritedFiltersFromUrl } = syncFilters(
deps.kbnUrlStateStorage,
deps.npDataStart.query.filterManager,
deps.npDataStart.query.timefilter.timefilter
);

// hack: always keeping the latest 'hasInheritedGlobalState' value in the same object - hasInheritedGlobalStateRef
// this is needed so angular Controller picks up the latest value, as it has reference to the hasInheritedGlobalStateRef
hasInheritedGlobalStateRef.value = hasInheritedGlobalState;
hasInheritedGlobalStateRef.value = hasInheritedFiltersFromUrl;
deps.hasInheritedGlobalState = hasInheritedGlobalStateRef;

if (!angularModuleInstance) {
Expand All @@ -106,7 +108,7 @@ export const renderApp = (element: HTMLElement, appBasePath: string, deps: Rende

return () => {
$injector.get('$rootScope').$destroy();
destroyGlobalState();
stopSyncingGlobalFilters();
};
};

Expand Down
1 change: 1 addition & 0 deletions src/plugins/data/public/query/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export * from './filter_manager';
export * from './timefilter';
export * from './saved_query';
export * from './persisted_log';
export * from './state_sync';
20 changes: 20 additions & 0 deletions src/plugins/data/public/query/state_sync/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export { syncFilters } from './sync_filters';
Original file line number Diff line number Diff line change
Expand Up @@ -19,84 +19,79 @@

import { Subscription } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import {
esFilters,
FilterManager,
RefreshInterval,
TimefilterContract,
TimeRange,
} from '../../../../../../plugins/data/public';
import {
createStateContainer,
IKbnUrlStateStorage,
syncState,
} from '../../../../../../plugins/kibana_utils/public';
import {
compareFilters,
COMPARE_ALL_OPTIONS,
// this whole file will soon be deprecated by new state management.
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../../../../plugins/data/public/query/filter_manager/lib/compare_filters';
} from '../../../../kibana_utils/public';
import { COMPARE_ALL_OPTIONS, compareFilters } from '../filter_manager/lib/compare_filters';
import { RefreshInterval, TimeRange, esFilters } from '../../../common';
import { FilterManager } from '../filter_manager';
import { TimefilterContract } from '../timefilter';

const GLOBAL_STATE_STORAGE_KEY = '_g';

export interface KbnGlobalState {
export interface FiltersSyncState {
time?: TimeRange;
refreshInterval?: RefreshInterval;
filters?: esFilters.Filter[];
}

// this should go away after: https://github.com/elastic/kibana/issues/55339
// is resolved
export const initGlobalState = (
/**
* Helper function to set up syncing between Filter & Time Filter services with url's '_g' query param
* @param urlStateStorage - url state storage to use
* @param filterManager - filter manager instance
* @param timeFilter - time filter instance
*/
export const syncFilters = (
urlStateStorage: IKbnUrlStateStorage,
filterManager: FilterManager,
timeFilter: TimefilterContract
) => {
const defaultState: KbnGlobalState = {
const defaultState: FiltersSyncState = {
time: timeFilter.getTime(),
refreshInterval: timeFilter.getRefreshInterval(),
filters: filterManager.getGlobalFilters(),
};

const initialStateFromUrl = urlStateStorage.get<KbnGlobalState>(GLOBAL_STATE_STORAGE_KEY);
const initialStateFromUrl = urlStateStorage.get<FiltersSyncState>(GLOBAL_STATE_STORAGE_KEY);

// remember whether there were info in the URL
const hasInheritedGlobalState = Boolean(
const hasInheritedFiltersFromUrl = Boolean(
initialStateFromUrl && Object.keys(initialStateFromUrl).length
);

const initialState: KbnGlobalState = {
const initialState: FiltersSyncState = {
...defaultState,
...initialStateFromUrl,
};

const globalStateContainer = createStateContainer(
const filtersSyncStateContainer = createStateContainer(
initialState,
{
setTime: (state: KbnGlobalState) => (time: TimeRange) => ({ ...state, time }),
setRefreshInterval: (state: KbnGlobalState) => (refreshInterval: RefreshInterval) => ({
setTime: (state: FiltersSyncState) => (time: TimeRange) => ({ ...state, time }),
setRefreshInterval: (state: FiltersSyncState) => (refreshInterval: RefreshInterval) => ({
...state,
refreshInterval,
}),
setFilters: (state: KbnGlobalState) => (filters: esFilters.Filter[]) => ({
setFilters: (state: FiltersSyncState) => (filters: esFilters.Filter[]) => ({
...state,
filters,
}),
},
{
time: (state: KbnGlobalState) => () => state.time,
refreshInterval: (state: KbnGlobalState) => () => state.refreshInterval,
filters: (state: KbnGlobalState) => () => state.filters,
time: (state: FiltersSyncState) => () => state.time,
refreshInterval: (state: FiltersSyncState) => () => state.refreshInterval,
filters: (state: FiltersSyncState) => () => state.filters,
}
);

const subs: Subscription[] = [
timeFilter.getTimeUpdate$().subscribe(() => {
globalStateContainer.transitions.setTime(timeFilter.getTime());
filtersSyncStateContainer.transitions.setTime(timeFilter.getTime());
}),
timeFilter.getRefreshIntervalUpdate$().subscribe(() => {
globalStateContainer.transitions.setRefreshInterval(timeFilter.getRefreshInterval());
filtersSyncStateContainer.transitions.setRefreshInterval(timeFilter.getRefreshInterval());
}),
filterManager
.getUpdates$()
Expand All @@ -105,52 +100,55 @@ export const initGlobalState = (
filter(newGlobalFilters => {
// continue only if global filters changed
// and ignore app state filters
const oldGlobalFilters = globalStateContainer.get().filters;
const oldGlobalFilters = filtersSyncStateContainer.get().filters;
return (
!oldGlobalFilters ||
!compareFilters(newGlobalFilters, oldGlobalFilters, COMPARE_ALL_OPTIONS)
);
})
)
.subscribe(newGlobalFilters => {
globalStateContainer.transitions.setFilters(newGlobalFilters);
filtersSyncStateContainer.transitions.setFilters(newGlobalFilters);
}),
globalStateContainer.state$
.pipe(map(state => _.cloneDeep(state))) // state in state container is 'frozen', but services implementations are mutating it
.subscribe(({ time, filters: globalFilters, refreshInterval }) => {
filtersSyncStateContainer.state$.subscribe(
({ time, filters: globalFilters, refreshInterval }) => {
if (time && !_.isEqual(time, timeFilter.getTime())) {
timeFilter.setTime(time);
timeFilter.setTime(_.cloneDeep(time));
}

if (refreshInterval && !_.isEqual(refreshInterval, timeFilter.getRefreshInterval())) {
timeFilter.setRefreshInterval(refreshInterval);
timeFilter.setRefreshInterval(_.cloneDeep(refreshInterval));
}

if (
globalFilters &&
!compareFilters(globalFilters, filterManager.getGlobalFilters(), COMPARE_ALL_OPTIONS)
) {
// have to make sure we don't accidentally remove application filters here
globalFilters = _.cloneDeep(globalFilters);
FilterManager.setFiltersStore(globalFilters, esFilters.FilterStateStore.GLOBAL_STATE);
// have to make sure we don't accidentally remove application filters here
filterManager.setFilters([...globalFilters, ...filterManager.getAppFilters()]);
}
}),
}
),
];

if (!initialStateFromUrl) {
urlStateStorage.set<KbnGlobalState>(GLOBAL_STATE_STORAGE_KEY, initialState, { replace: true });
urlStateStorage.set<FiltersSyncState>(GLOBAL_STATE_STORAGE_KEY, initialState, {
replace: true,
});
}

// trigger syncing from state container to services if needed
globalStateContainer.set(initialState);
filtersSyncStateContainer.set(initialState);

const { start, stop } = syncState({
stateStorage: urlStateStorage,
stateContainer: {
...globalStateContainer,
...filtersSyncStateContainer,
set: state => {
globalStateContainer.set({
...globalStateContainer.get(),
filtersSyncStateContainer.set({
...filtersSyncStateContainer.get(),
...state,
});
},
Expand All @@ -160,10 +158,10 @@ export const initGlobalState = (

start();
return {
destroy: () => {
stop: () => {
subs.forEach(s => s.unsubscribe());
stop();
},
hasInheritedGlobalState,
hasInheritedFiltersFromUrl,
};
};

0 comments on commit b60cad0

Please sign in to comment.