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

[State Management] - Remove GlobalState from dashboard #55158

Merged
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,7 @@ import _ from 'lodash';
import { Subscription } from 'rxjs';
import { State } from 'ui/state_management/state';
import { FilterManager, esFilters } from '../../../../../../plugins/data/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';
import { compareFilters, COMPARE_ALL_OPTIONS } from '../../../../../../plugins/data/public';

type GetAppStateFunc = () => { filters?: esFilters.Filter[]; save?: () => void } | undefined | null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import chrome from 'ui/chrome';

export const legacyChrome = chrome;
export { State } from 'ui/state_management/state';
export { SavedObjectSaveOpts } from 'ui/saved_objects/types';
export { npSetup, npStart } from 'ui/new_platform';
export { IPrivate } from 'ui/private';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,43 @@

import { EuiConfirmModal, EuiIcon } from '@elastic/eui';
import angular, { IModule } from 'angular';
import { createHashHistory, History } from 'history';
import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular';
import {
AppMountContext,
ChromeStart,
IUiSettingsClient,
LegacyCoreStart,
SavedObjectsClientContract,
IUiSettingsClient,
} from 'kibana/public';
import { Storage } from '../../../../../../plugins/kibana_utils/public';
import {
GlobalStateProvider,
StateManagementConfigProvider,
PrivateProvider,
EventsProvider,
PersistedState,
createKbnUrlStateStorage,
IKbnUrlStateStorage,
Storage,
} from '../../../../../../plugins/kibana_utils/public';
import {
configureAppAngularModule,
confirmModalFactory,
createTopNavDirective,
createTopNavHelper,
PromiseServiceCreator,
EventsProvider,
IPrivate,
KbnUrlProvider,
PersistedState,
PrivateProvider,
PromiseServiceCreator,
RedirectWhenMissingProvider,
confirmModalFactory,
configureAppAngularModule,
SavedObjectLoader,
IPrivate,
StateManagementConfigProvider,
} from '../legacy_imports';

// @ts-ignore
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,
syncQuery,
} from '../../../../../../plugins/data/public';
import { SharePluginStart } from '../../../../../../plugins/share/public';

export interface RenderDeps {
Expand All @@ -67,21 +73,57 @@ export interface RenderDeps {
embeddables: IEmbeddableStart;
localStorage: Storage;
share: SharePluginStart;

// hack:
// renderApp() called each time the app is mounted,
// but initialisation of angular module happens only once.
// On each app mount, new history and kbnUrlStateStorage instances are created
// and we have to make sure, that those new instances will be used in DashboardAppController
// and not the ones which where created when angular module was initialised.
// This is achieved by having reference to initial object, which angular module was initialised with,
// and then by mutating that object on subsequent mounts we can pass new instances down to controller.
// If not for this hack, we could end up that global state syncing uses different history instance then app state syncing
// Which could cause excessive browser history entries.
angularGlobalStateHacks?: AngularGlobalStateHacks;
}

interface AngularGlobalStateHacks {
hasInheritedGlobalState?: boolean;
kbnUrlStateStorage?: IKbnUrlStateStorage;
history?: History;
}
let angularModuleInstance: IModule | null = null;
const angularGlobalStateHacks: AngularGlobalStateHacks = {};

export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => {
const history = createHashHistory();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We could put that part into a function initGlobalStateHandling that is called in the dashboard controller in src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx and in the listing controller in src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js - initGlobalStateHandling would return a function that stops the syncing and can be registered within the controller using $scope.$on('$destroy', () => {. Then we don't need a reference on the application level and don't need to overwrite typescripts type checks

Copy link
Contributor Author

@Dosant Dosant Jan 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, looks better 👍
Hope I got it right.
3a51104

const kbnUrlStateStorage = createKbnUrlStateStorage({
history,
useHash: deps.uiSettings.get('state:storeInSessionStorage'),
});
const { stop: stopSyncingGlobalState, hasInheritedQueryFromUrl } = syncQuery(
deps.npDataStart.query,
kbnUrlStateStorage
);

angularGlobalStateHacks.hasInheritedGlobalState = hasInheritedQueryFromUrl;
angularGlobalStateHacks.history = history;
angularGlobalStateHacks.kbnUrlStateStorage = kbnUrlStateStorage;

if (!angularModuleInstance) {
angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation);
// global routing stuff
configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart, true);
// custom routing stuff
deps.angularGlobalStateHacks = angularGlobalStateHacks;
initDashboardApp(angularModuleInstance, deps);
}

const $injector = mountDashboardApp(appBasePath, element);

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

Expand Down Expand Up @@ -146,17 +188,13 @@ function createLocalConfirmModalModule() {
}

function createLocalStateModule() {
angular
.module('app/dashboard/State', [
'app/dashboard/Private',
'app/dashboard/Config',
'app/dashboard/KbnUrl',
'app/dashboard/Promise',
'app/dashboard/PersistedState',
])
.service('globalState', function(Private: any) {
return Private(GlobalStateProvider);
});
angular.module('app/dashboard/State', [
'app/dashboard/Private',
'app/dashboard/Config',
'app/dashboard/KbnUrl',
'app/dashboard/Promise',
'app/dashboard/PersistedState',
]);
}

function createLocalPersistedStateModule() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,12 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) {
$route: any,
$routeParams: {
id?: string;
},
globalState: any
}
) =>
new DashboardAppController({
$route,
$scope,
$routeParams,
globalState,
config,
confirmModal,
indexPatterns: deps.npDataStart.indexPatterns,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import React from 'react';
import angular from 'angular';

import { Subscription } from 'rxjs';
import { createHashHistory } from 'history';
import { map } from 'rxjs/operators';
import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen';

import {
Expand All @@ -32,16 +32,16 @@ import {
SavedObjectSaveOpts,
SaveResult,
showSaveModal,
State,
subscribeWithScope,
} from '../legacy_imports';
import { FilterStateManager } from '../../../../data/public';
import {
esFilters,
COMPARE_ALL_OPTIONS,
compareFilters,
IndexPattern,
IndexPatternsContract,
Query,
SavedQuery,
syncAppFilters,
} from '../../../../../../plugins/data/public';

import {
Expand Down Expand Up @@ -82,7 +82,6 @@ export interface DashboardAppControllerDependencies extends RenderDeps {
$scope: DashboardAppScope;
$route: any;
$routeParams: any;
globalState: State;
indexPatterns: IndexPatternsContract;
dashboardConfig: any;
config: any;
Expand All @@ -99,7 +98,6 @@ export class DashboardAppController {
$scope,
$route,
$routeParams,
globalState,
dashboardConfig,
localStorage,
indexPatterns,
Expand All @@ -116,7 +114,12 @@ export class DashboardAppController {
},
},
core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects, http },
angularGlobalStateHacks,
}: DashboardAppControllerDependencies) {
const history = angularGlobalStateHacks!.history!;
const kbnUrlStateStorage = angularGlobalStateHacks!.kbnUrlStateStorage!;
const hasInheritedGlobalState = angularGlobalStateHacks!.hasInheritedGlobalState!;

const queryFilter = filterManager;

let lastReloadRequestTime = 0;
Expand All @@ -126,34 +129,23 @@ export class DashboardAppController {
chrome.docTitle.change(dash.title);
}

const history = createHashHistory();
const dashboardStateManager = new DashboardStateManager({
savedDashboard: dash,
useHashedUrl: config.get('state:storeInSessionStorage'),
hideWriteControls: dashboardConfig.getHideWriteControls(),
kibanaVersion: injectedMetadata.getKibanaVersion(),
kbnUrlStateStorage,
history,
});

const filterStateManager = new FilterStateManager(
globalState,
() => {
// Temporary AppState replacement
return {
set filters(_filters: esFilters.Filter[]) {
dashboardStateManager.setFilters(_filters);
},
get filters() {
return dashboardStateManager.appState.filters;
},
};
},
filterManager
);
const stopSyncingAppFilters = syncAppFilters(filterManager, {
set: filters => dashboardStateManager.setFilters(filters),
get: () => dashboardStateManager.appState.filters,
state$: dashboardStateManager.appState$.pipe(map(state => state.filters)),
});

// The hash check is so we only update the time filter on dashboard open, not during
// normal cross app navigation.
if (dashboardStateManager.getIsTimeSavedWithDashboard() && !globalState.$inheritedGlobalState) {
if (dashboardStateManager.getIsTimeSavedWithDashboard() && !hasInheritedGlobalState) {
dashboardStateManager.syncTimefilterWithDashboard(timefilter);
}
$scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean;
Expand Down Expand Up @@ -411,7 +403,15 @@ export class DashboardAppController {

const containerInput = dashboardContainer.getInput();
const differences: Partial<DashboardContainerInput> = {};
Object.keys(containerInput).forEach(key => {

// Filters shouldn't be compared using regular isEqual
if (
!compareFilters(containerInput.filters, appStateDashboardInput.filters, COMPARE_ALL_OPTIONS)
) {
differences.filters = appStateDashboardInput.filters;
}

Object.keys(_.omit(containerInput, 'filters')).forEach(key => {
const containerValue = (containerInput as { [key: string]: unknown })[key];
const appStateValue = ((appStateDashboardInput as unknown) as { [key: string]: unknown })[
key
Expand Down Expand Up @@ -878,6 +878,7 @@ export class DashboardAppController {

$scope.$on('$destroy', () => {
updateSubscription.unsubscribe();
stopSyncingAppFilters();
visibleSubscription.unsubscribe();
$scope.timefilterSubscriptions$.unsubscribe();

Expand All @@ -891,9 +892,6 @@ export class DashboardAppController {
if (dashboardContainer) {
dashboardContainer.destroy();
}
if (filterStateManager) {
filterStateManager.destroy();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { DashboardStateManager } from './dashboard_state_manager';
import { getSavedDashboardMock } from '../__tests__';
import { InputTimeRange, TimefilterContract, TimeRange } from 'src/plugins/data/public';
import { ViewMode } from 'src/plugins/embeddable/public';
import { createKbnUrlStateStorage } from 'src/plugins/kibana_utils/public';

jest.mock('ui/agg_types', () => ({
aggTypes: {
Expand All @@ -48,9 +49,9 @@ describe('DashboardState', function() {
function initDashboardState() {
dashboardState = new DashboardStateManager({
savedDashboard,
useHashedUrl: false,
hideWriteControls: false,
kibanaVersion: '7.0.0',
kbnUrlStateStorage: createKbnUrlStateStorage(),
history: createBrowserHistory(),
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@

import { i18n } from '@kbn/i18n';
import _ from 'lodash';
import { History } from 'history';
import { Subscription } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { Moment } from 'moment';
import { History } from 'history';

import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public';
import { ViewMode } from '../../../../../../plugins/embeddable/public';
Expand All @@ -44,7 +44,6 @@ import {
SavedDashboardPanel,
} from './types';
import {
createKbnUrlStateStorage,
createStateContainer,
IKbnUrlStateStorage,
ISyncStateRef,
Expand Down Expand Up @@ -76,6 +75,10 @@ export class DashboardStateManager {
return this.stateContainer.get();
}

public get appState$(): Observable<DashboardAppState> {
return this.stateContainer.state$;
}

private readonly stateContainer: ReduxLikeStateContainer<
DashboardAppState,
DashboardAppStateTransitions
Expand All @@ -97,13 +100,13 @@ export class DashboardStateManager {
savedDashboard,
hideWriteControls,
kibanaVersion,
useHashedUrl,
kbnUrlStateStorage,
history,
}: {
savedDashboard: SavedObjectDashboard;
hideWriteControls: boolean;
kibanaVersion: string;
useHashedUrl: boolean;
kbnUrlStateStorage: IKbnUrlStateStorage;
history: History;
}) {
this.history = history;
Expand All @@ -117,7 +120,7 @@ export class DashboardStateManager {
kibanaVersion
);

this.kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: useHashedUrl, history });
this.kbnUrlStateStorage = kbnUrlStateStorage;

// setup initial state by merging defaults with state from url
// also run migration, as state in url could be of older version
Expand Down
Loading