Skip to content

Commit

Permalink
Remove AppState from np/dashboard
Browse files Browse the repository at this point in the history
improve
  • Loading branch information
Dosant committed Jan 14, 2020
1 parent 038c2b1 commit c7a861e
Show file tree
Hide file tree
Showing 19 changed files with 435 additions and 240 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,20 @@
*/

import _ from 'lodash';
import { Subscription } from 'rxjs';
import { State } from 'ui/state_management/state';
import { FilterManager, esFilters } from '../../../../../../plugins/data/public';

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

/**
* FilterStateManager is responsible for watching for filter changes
* and syncing with FilterManager, as well as syncing FilterManager changes
* back to the URL.
**/
export class FilterStateManager {
private subs: Subscription[] = [];

filterManager: FilterManager;
globalState: State;
getAppState: GetAppStateFunc;
Expand All @@ -41,15 +44,18 @@ export class FilterStateManager {

this.watchFilterState();

this.filterManager.getUpdates$().subscribe(() => {
this.updateAppState();
});
this.subs.push(
this.filterManager.getUpdates$().subscribe(() => {
this.updateAppState();
})
);
}

destroy() {
if (this.interval) {
clearInterval(this.interval);
}
this.subs.forEach(s => s.unsubscribe());
}

private watchFilterState() {
Expand Down Expand Up @@ -80,7 +86,7 @@ export class FilterStateManager {

private saveState() {
const appState = this.getAppState();
if (appState) appState.save();
if (appState && appState.save) appState.save();
this.globalState.save();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,5 @@
* under the License.
*/

export { getAppStateMock } from './get_app_state_mock';
export { getSavedDashboardMock } from './get_saved_dashboard_mock';
export { getEmbeddableFactoryMock } from './get_embeddable_factories_mock';
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,7 @@
import moment from 'moment';
import { Subscription } from 'rxjs';

import {
AppStateClass as TAppStateClass,
AppState as TAppState,
IInjector,
KbnUrl,
} from '../legacy_imports';
import { IInjector } from '../legacy_imports';

import { ViewMode } from '../../../../embeddable_api/public/np_ready/public';
import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard';
Expand All @@ -43,7 +38,7 @@ import { RenderDeps } from './application';

export interface DashboardAppScope extends ng.IScope {
dash: SavedObjectDashboard;
appState: TAppState;
appState: DashboardAppState;
screenTitle: string;
model: {
query: Query;
Expand All @@ -60,7 +55,6 @@ export interface DashboardAppScope extends ng.IScope {
refreshInterval: any;
panels: SavedDashboardPanel[];
indexPatterns: IIndexPattern[];
$evalAsync: any;
dashboardViewMode: ViewMode;
expandedPanel?: string;
getShouldShowEditHelp: () => boolean;
Expand Down Expand Up @@ -91,8 +85,6 @@ export interface DashboardAppScope extends ng.IScope {

export function initDashboardAppDirective(app: any, deps: RenderDeps) {
app.directive('dashboardApp', function($injector: IInjector) {
const AppState = $injector.get<TAppStateClass<DashboardAppState>>('AppState');
const kbnUrl = $injector.get<KbnUrl>('kbnUrl');
const confirmModal = $injector.get<ConfirmModalFn>('confirmModal');
const config = deps.uiSettings;

Expand All @@ -105,17 +97,13 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) {
$routeParams: {
id?: string;
},
getAppState: any,
globalState: any
) =>
new DashboardAppController({
$route,
$scope,
$routeParams,
getAppState,
globalState,
kbnUrl,
AppStateClass: AppState,
config,
confirmModal,
indexPatterns: deps.npDataStart.indexPatterns,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,57 +17,56 @@
* under the License.
*/

import _ from 'lodash';
import _, { uniq } from 'lodash';
import { i18n } from '@kbn/i18n';
import React from 'react';
import angular from 'angular';
import { uniq } from 'lodash';

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

import {
subscribeWithScope,
ConfirmationButtonTypes,
showSaveModal,
SaveResult,
migrateLegacyQuery,
State,
AppStateClass as TAppStateClass,
KbnUrl,
SavedObjectSaveOpts,
SaveResult,
showSaveModal,
State,
subscribeWithScope,
unhashUrl,
} from '../legacy_imports';
import { FilterStateManager } from '../../../../data/public';
import {
esFilters,
IndexPattern,
IndexPatternsContract,
Query,
SavedQuery,
IndexPatternsContract,
} from '../../../../../../plugins/data/public';

import {
DashboardContainer,
DASHBOARD_CONTAINER_TYPE,
DashboardContainer,
DashboardContainerFactory,
DashboardContainerInput,
DashboardPanelState,
} from '../../../../dashboard_embeddable_container/public/np_ready/public';
import {
isErrorEmbeddable,
EmbeddableFactoryNotFoundError,
ErrorEmbeddable,
ViewMode,
isErrorEmbeddable,
openAddPanelFlyout,
EmbeddableFactoryNotFoundError,
ViewMode,
} from '../../../../embeddable_api/public/np_ready/public';
import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types';
import { ConfirmModalFn, NavAction, SavedDashboardPanel } from './types';

import { showOptionsPopover } from './top_nav/show_options_popover';
import { DashboardSaveModal } from './top_nav/save_modal';
import { showCloneModal } from './top_nav/show_clone_modal';
import { saveDashboard } from './lib';
import { DashboardStateManager } from './dashboard_state_manager';
import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants';
import { createDashboardEditUrl, DashboardConstants } from './dashboard_constants';
import { getTopNavConfig } from './top_nav/get_top_nav_config';
import { TopNavIds } from './top_nav/top_nav_ids';
import { getDashboardTitle } from './dashboard_strings';
Expand All @@ -78,17 +77,15 @@ import {
SavedObjectFinderProps,
SavedObjectFinderUi,
} from '../../../../../../plugins/kibana_react/public';
import { removeQueryParam } from '../../../../../../plugins/kibana_utils/public';

export interface DashboardAppControllerDependencies extends RenderDeps {
$scope: DashboardAppScope;
$route: any;
$routeParams: any;
getAppState: any;
globalState: State;
indexPatterns: IndexPatternsContract;
dashboardConfig: any;
kbnUrl: KbnUrl;
AppStateClass: TAppStateClass<DashboardAppState>;
config: any;
confirmModal: ConfirmModalFn;
}
Expand All @@ -103,12 +100,9 @@ export class DashboardAppController {
$scope,
$route,
$routeParams,
getAppState,
globalState,
dashboardConfig,
localStorage,
kbnUrl,
AppStateClass,
indexPatterns,
config,
confirmModal,
Expand All @@ -124,7 +118,6 @@ export class DashboardAppController {
},
core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects, http },
}: DashboardAppControllerDependencies) {
new FilterStateManager(globalState, getAppState, filterManager);
const queryFilter = filterManager;

let lastReloadRequestTime = 0;
Expand All @@ -134,14 +127,30 @@ export class DashboardAppController {
chrome.docTitle.change(dash.title);
}

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

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

// The hash check is so we only update the time filter on dashboard open, not during
// normal cross app navigation.
Expand Down Expand Up @@ -316,8 +325,8 @@ export class DashboardAppController {
dirty = true;
}

dashboardStateManager.handleDashboardContainerChanges(container);
$scope.$evalAsync(() => {
dashboardStateManager.handleDashboardContainerChanges(container);
if (dirty) {
updateState();
}
Expand All @@ -337,8 +346,8 @@ export class DashboardAppController {
const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE];
const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID];
container.addSavedObjectEmbeddable(type, id);
kbnUrl.removeParam(DashboardConstants.ADD_EMBEDDABLE_TYPE);
kbnUrl.removeParam(DashboardConstants.ADD_EMBEDDABLE_ID);
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE);
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID);
}
}

Expand Down Expand Up @@ -409,7 +418,9 @@ export class DashboardAppController {
key
];
if (!_.isEqual(containerValue, appStateValue)) {
(differences as { [key: string]: unknown })[key] = appStateValue;
// cloneDeep hack is needed, as there are multiple place, where container's input mutated,
// but values from appStateValue are deeply frozen, as they can't be mutated directly
(differences as { [key: string]: unknown })[key] = _.cloneDeep(appStateValue);
}
});

Expand Down Expand Up @@ -580,9 +591,6 @@ export class DashboardAppController {

function revertChangesAndExitEditMode() {
dashboardStateManager.resetState();
kbnUrl.change(
dash.id ? createDashboardEditUrl(dash.id) : DashboardConstants.CREATE_NEW_DASHBOARD_URL
);
// This is only necessary for new dashboards, which will default to Edit mode.
updateViewMode(ViewMode.VIEW);

Expand All @@ -592,6 +600,17 @@ export class DashboardAppController {
if (dashboardStateManager.getIsTimeSavedWithDashboard()) {
dashboardStateManager.syncTimefilterWithDashboard(timefilter);
}

// TODO: find nicer solution for those:
// synchronously persist current state to url with push()
dashboardStateManager.saveState({ replace: false });
// change pathname
history.replace({
...history.location,
pathname: dash.id
? createDashboardEditUrl(dash.id)
: DashboardConstants.CREATE_NEW_DASHBOARD_URL,
});
}

confirmModal(
Expand Down Expand Up @@ -642,7 +661,20 @@ export class DashboardAppController {
});

if (dash.id !== $routeParams.id) {
kbnUrl.change(createDashboardEditUrl(dash.id));
// kbnUrl.change(createDashboardEditUrl(dash.id))
// Angular's $location skips this update because of history updates from syncState which happen simultaneously
// when calling kbnUrl.change() angular schedules url update and when angular finally starts to process it,
// the update is considered outdated and angular skips it

// TODO: find nicer solution for those below:

// synchronously write current state to ?_a with push()
dashboardStateManager.saveState({ replace: false });
// change pathname to -> /dashboard/:id
history.replace({
...history.location,
pathname: createDashboardEditUrl(dash.id),
});
} else {
chrome.docTitle.change(dash.lastSavedTitle);
updateViewMode(ViewMode.VIEW);
Expand Down Expand Up @@ -859,6 +891,9 @@ export class DashboardAppController {
if (dashboardContainer) {
dashboardContainer.destroy();
}
if (filterStateManager) {
filterStateManager.destroy();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,10 @@

import './np_core.test.mocks';
import { DashboardStateManager } from './dashboard_state_manager';
import { getAppStateMock, getSavedDashboardMock } from '../__tests__';
import { AppStateClass } from '../legacy_imports';
import { DashboardAppState } from './types';
import { TimeRange, TimefilterContract, InputTimeRange } from 'src/plugins/data/public';
import { getSavedDashboardMock } from '../__tests__';
import { InputTimeRange, TimefilterContract, TimeRange } from 'src/plugins/data/public';
import { ViewMode } from 'src/plugins/embeddable/public';

jest.mock('ui/state_management/state', () => ({
State: {},
}));

describe('DashboardState', function() {
let dashboardState: DashboardStateManager;
const savedDashboard = getSavedDashboardMock();
Expand All @@ -46,7 +40,7 @@ describe('DashboardState', function() {
function initDashboardState() {
dashboardState = new DashboardStateManager({
savedDashboard,
AppStateClass: getAppStateMock() as AppStateClass<DashboardAppState>,
useHashedUrl: false,
hideWriteControls: false,
kibanaVersion: '7.0.0',
});
Expand Down
Loading

0 comments on commit c7a861e

Please sign in to comment.