Skip to content

Commit

Permalink
Add global state to stack monitoring react app
Browse files Browse the repository at this point in the history
  • Loading branch information
estermv committed Aug 23, 2021
1 parent 9a7ebb7 commit f26f958
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 React, { createContext } from 'react';
import { GlobalState } from '../url_state';
import { MonitoringStartPluginDependencies } from '../types';

export const GlobalStateContext = createContext({});

interface GlobalStateProviderProps {
query: MonitoringStartPluginDependencies['data']['query'];
toasts: MonitoringStartPluginDependencies['core']['notifications']['toasts'];
children: React.ReactNode;
}

export const GlobalStateProvider = ({ query, toasts, children }: GlobalStateProviderProps) => {
const fakeRootScope: Partial<ng.IRootScopeService> = {
$on: (
name: string,
listener: (event: ng.IAngularEvent, ...args: any[]) => any
): (() => void) => () => {},
$applyAsync: () => {},
};

const fakeAngularLocation: Partial<ng.ILocationService> = {
search: () => {
return {} as any;
},
replace: () => {
return {} as any;
},
};

const localState: { [key: string]: unknown } = {};
const state = new GlobalState(query, toasts, fakeRootScope, fakeAngularLocation, localState);

const initialState: any = state.getState();
for (const key in initialState) {
if (!initialState.hasOwnProperty(key)) {
continue;
}
localState[key] = initialState[key];
}

localState.save = () => {
const newState = { ...localState };
delete newState.save;
state.setState(newState);
};

return <GlobalStateContext.Provider value={localState}>{children}</GlobalStateContext.Provider>;
};
35 changes: 21 additions & 14 deletions x-pack/plugins/monitoring/public/application/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import { CoreStart, AppMountParameters } from 'kibana/public';
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Switch, Redirect, HashRouter } from 'react-router-dom';
import { Route, Switch, Redirect, Router } from 'react-router-dom';
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
import { LoadingPage } from './pages/loading_page';
import { MonitoringStartPluginDependencies } from '../types';
import { GlobalStateProvider } from './global_state_context';
import { createPreserveQueryHistory } from './preserve_query_history';

export const renderApp = (
core: CoreStart,
Expand All @@ -29,21 +31,26 @@ const MonitoringApp: React.FC<{
core: CoreStart;
plugins: MonitoringStartPluginDependencies;
}> = ({ core, plugins }) => {
const history = createPreserveQueryHistory();

return (
<KibanaContextProvider services={{ ...core, ...plugins }}>
<HashRouter>
<Switch>
<Route path="/loading" component={LoadingPage} />
<Route path="/no-data" component={NoData} />
<Route path="/home" component={Home} />
<Route path="/overview" component={ClusterOverview} />
<Redirect
to={{
pathname: '/loading',
}}
/>
</Switch>
</HashRouter>
<GlobalStateProvider query={plugins.data.query} toasts={core.notifications.toasts}>
<Router history={history}>
<Switch>
<Route path="/loading" component={LoadingPage} />
<Route path="/no-data" component={NoData} />
<Route path="/home" component={Home} />
<Route path="/overview" component={ClusterOverview} />
<Redirect
to={{
pathname: '/loading',
search: history.location.search,
}}
/>
</Switch>
</Router>
</GlobalStateProvider>
</KibanaContextProvider>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 { History, createHashHistory, LocationDescriptor, LocationDescriptorObject } from 'history';

function preserveQueryParameters(
history: History,
location: LocationDescriptorObject
): LocationDescriptorObject {
location.search = history.location.search;
return location;
}

function createLocationDescriptorObject(
location: LocationDescriptor,
state?: History.LocationState
): LocationDescriptorObject {
return typeof location === 'string' ? { pathname: location, state } : location;
}

export function createPreserveQueryHistory() {
const history = createHashHistory({ hashType: 'slash' });
const oldPush = history.push;
const oldReplace = history.replace;
history.push = (path: LocationDescriptor, state?: History.LocationState) =>
oldPush.apply(history, [
preserveQueryParameters(history, createLocationDescriptorObject(path, state)),
]);
history.replace = (path: LocationDescriptor, state?: History.LocationState) =>
oldReplace.apply(history, [
preserveQueryParameters(history, createLocationDescriptorObject(path, state)),
]);
return history;
}
4 changes: 4 additions & 0 deletions x-pack/plugins/monitoring/public/legacy_shims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,8 @@ export class Legacy {
}
return Legacy._shims;
}

public static isInitializated(): boolean {
return Boolean(Legacy._shims);
}
}
26 changes: 17 additions & 9 deletions x-pack/plugins/monitoring/public/url_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ export class GlobalState {
constructor(
queryService: MonitoringStartPluginDependencies['data']['query'],
toasts: MonitoringStartPluginDependencies['core']['notifications']['toasts'],
rootScope: ng.IRootScopeService,
ngLocation: ng.ILocationService,
rootScope: Partial<ng.IRootScopeService>,
ngLocation: Partial<ng.ILocationService>,
externalState: RawObject
) {
this.timefilterRef = queryService.timefilter.timefilter;
Expand All @@ -102,19 +102,24 @@ export class GlobalState {
this.stateContainerChangeSub = this.stateContainer.state$.subscribe(() => {
this.lastAssignedState = this.getState();
if (!this.stateContainer.get() && this.lastKnownGlobalState) {
ngLocation.search(`${GLOBAL_STATE_KEY}=${this.lastKnownGlobalState}`).replace();
ngLocation.search?.(`${GLOBAL_STATE_KEY}=${this.lastKnownGlobalState}`).replace();
}
Legacy.shims.breadcrumbs.update();

// TODO: check if this is not needed after https://github.com/elastic/kibana/pull/109132 is merged
if (Legacy.isInitializated()) {
Legacy.shims.breadcrumbs.update();
}

this.syncExternalState(externalState);
rootScope.$applyAsync();
rootScope.$applyAsync?.();
});

this.syncQueryStateWithUrlManager = syncQueryStateWithUrl(queryService, this.stateStorage);
this.stateSyncRef.start();
this.startHashSync(rootScope, ngLocation);
this.lastAssignedState = this.getState();

rootScope.$on('$destroy', () => this.destroy());
rootScope.$on?.('$destroy', () => this.destroy());
}

private syncExternalState(externalState: { [key: string]: unknown }) {
Expand All @@ -131,15 +136,18 @@ export class GlobalState {
}
}

private startHashSync(rootScope: ng.IRootScopeService, ngLocation: ng.ILocationService) {
rootScope.$on(
private startHashSync(
rootScope: Partial<ng.IRootScopeService>,
ngLocation: Partial<ng.ILocationService>
) {
rootScope.$on?.(
'$routeChangeStart',
(_: { preventDefault: () => void }, newState: Route, oldState: Route) => {
const currentGlobalState = oldState?.params?._g;
const nextGlobalState = newState?.params?._g;
if (!nextGlobalState && currentGlobalState && typeof currentGlobalState === 'string') {
newState.params._g = currentGlobalState;
ngLocation.search(`${GLOBAL_STATE_KEY}=${currentGlobalState}`).replace();
ngLocation.search?.(`${GLOBAL_STATE_KEY}=${currentGlobalState}`).replace();
}
this.lastKnownGlobalState = (nextGlobalState || currentGlobalState) as string;
}
Expand Down

0 comments on commit f26f958

Please sign in to comment.