diff --git a/x-pack/plugins/canvas/common/lib/constants.ts b/x-pack/plugins/canvas/common/lib/constants.ts index a37dc3fd6a7b3..f2155d9202939 100644 --- a/x-pack/plugins/canvas/common/lib/constants.ts +++ b/x-pack/plugins/canvas/common/lib/constants.ts @@ -18,7 +18,7 @@ export const API_ROUTE_WORKPAD_STRUCTURES = `${API_ROUTE}/workpad-structures`; export const API_ROUTE_CUSTOM_ELEMENT = `${API_ROUTE}/custom-element`; export const LOCALSTORAGE_PREFIX = `kibana.canvas`; export const LOCALSTORAGE_CLIPBOARD = `${LOCALSTORAGE_PREFIX}.clipboard`; -export const LOCALSTORAGE_LASTPAGE = 'canvas:lastpage'; +export const SESSIONSTORAGE_LASTPATH = 'lastPath:canvas'; export const FETCH_TIMEOUT = 30000; // 30 seconds export const CANVAS_USAGE_TYPE = 'canvas'; export const DEFAULT_WORKPAD_CSS = '.canvasPage {\n\n}'; diff --git a/x-pack/plugins/canvas/public/application.tsx b/x-pack/plugins/canvas/public/application.tsx index 284023e74d137..9c2aa821be2d5 100644 --- a/x-pack/plugins/canvas/public/application.tsx +++ b/x-pack/plugins/canvas/public/application.tsx @@ -10,8 +10,9 @@ import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { Provider } from 'react-redux'; +import { BehaviorSubject } from 'rxjs'; -import { AppMountParameters, CoreStart, CoreSetup } from 'kibana/public'; +import { AppMountParameters, CoreStart, CoreSetup, AppUpdater } from 'kibana/public'; import { CanvasStartDeps, CanvasSetupDeps } from './plugin'; // @ts-ignore Untyped local @@ -88,9 +89,10 @@ export const initializeCanvas = async ( coreStart: CoreStart, setupPlugins: CanvasSetupDeps, startPlugins: CanvasStartDeps, - registries: SetupRegistries + registries: SetupRegistries, + appUpdater: BehaviorSubject ) => { - startServices(coreSetup, coreStart, setupPlugins, startPlugins); + startServices(coreSetup, coreStart, setupPlugins, startPlugins, appUpdater); // Create Store const canvasStore = await createStore(coreSetup, setupPlugins); diff --git a/x-pack/plugins/canvas/public/components/app/index.js b/x-pack/plugins/canvas/public/components/app/index.js index de0d4c190eae6..750132dadb97d 100644 --- a/x-pack/plugins/canvas/public/components/app/index.js +++ b/x-pack/plugins/canvas/public/components/app/index.js @@ -8,6 +8,7 @@ import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; import { getAppReady, getBasePath } from '../../state/selectors/app'; import { appReady, appError } from '../../state/actions/app'; +import { withKibana } from '../../../../../../src/plugins/kibana_react/public'; import { App as Component } from './app'; @@ -44,7 +45,8 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { export const App = compose( connect(mapStateToProps, mapDispatchToProps, mergeProps), - withProps(() => ({ - onRouteChange: () => undefined, + withKibana, + withProps(props => ({ + onRouteChange: props.kibana.services.canvas.navLink.updatePath, })) )(Component); diff --git a/x-pack/plugins/canvas/public/lib/clipboard.ts b/x-pack/plugins/canvas/public/lib/clipboard.ts index 11755807aa533..cb940fd064a47 100644 --- a/x-pack/plugins/canvas/public/lib/clipboard.ts +++ b/x-pack/plugins/canvas/public/lib/clipboard.ts @@ -4,22 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { LOCALSTORAGE_CLIPBOARD } from '../../common/lib/constants'; -import { getWindow } from './get_window'; - -let storage: Storage; - -const getStorage = (): Storage => { - if (!storage) { - storage = new Storage(getWindow().localStorage); - } - - return storage; -}; +import { getLocalStorage } from './storage'; export const setClipboardData = (data: any) => { - getStorage().set(LOCALSTORAGE_CLIPBOARD, JSON.stringify(data)); + getLocalStorage().set(LOCALSTORAGE_CLIPBOARD, JSON.stringify(data)); }; -export const getClipboardData = () => getStorage().get(LOCALSTORAGE_CLIPBOARD); +export const getClipboardData = () => getLocalStorage().get(LOCALSTORAGE_CLIPBOARD); diff --git a/x-pack/plugins/canvas/public/lib/get_window.ts b/x-pack/plugins/canvas/public/lib/get_window.ts index 42c632f4a514f..c8fb035d4d33f 100644 --- a/x-pack/plugins/canvas/public/lib/get_window.ts +++ b/x-pack/plugins/canvas/public/lib/get_window.ts @@ -5,10 +5,18 @@ */ // return window if it exists, otherwise just return an object literal -const windowObj = { location: null, localStorage: {} as Window['localStorage'] }; +const windowObj = { + location: null, + localStorage: {} as Window['localStorage'], + sessionStorage: {} as Window['sessionStorage'], +}; export const getWindow = (): | Window - | { location: Location | null; localStorage: Window['localStorage'] } => { + | { + location: Location | null; + localStorage: Window['localStorage']; + sessionStorage: Window['sessionStorage']; + } => { return typeof window === 'undefined' ? windowObj : window; }; diff --git a/x-pack/plugins/canvas/public/lib/storage.ts b/x-pack/plugins/canvas/public/lib/storage.ts new file mode 100644 index 0000000000000..47c8cc741eaf3 --- /dev/null +++ b/x-pack/plugins/canvas/public/lib/storage.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { getWindow } from './get_window'; + +export enum StorageType { + Local = 'localStorage', + Session = 'sessionStorage', +} + +const storages: { + [x in StorageType]: Storage | null; +} = { + [StorageType.Local]: null, + [StorageType.Session]: null, +}; + +const getStorage = (type: StorageType): Storage => { + const storage = storages[type] || new Storage(getWindow()[type]); + storages[type] = storage; + + return storage; +}; + +export const getLocalStorage = (): Storage => { + return getStorage(StorageType.Local); +}; + +export const getSessionStorage = (): Storage => { + return getStorage(StorageType.Session); +}; diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index ba57d1475bc4f..c2192818e528b 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -4,15 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ +import { BehaviorSubject } from 'rxjs'; import { CoreSetup, CoreStart, Plugin, AppMountParameters, + AppUpdater, DEFAULT_APP_CATEGORIES, } from '../../../../src/core/public'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import { initLoadingIndicator } from './lib/loading_indicator'; +import { getSessionStorage } from './lib/storage'; +import { SESSIONSTORAGE_LASTPATH } from '../common/lib/constants'; import { featureCatalogueEntry } from './feature_catalogue_entry'; import { ExpressionsSetup, ExpressionsStart } from '../../../../src/plugins/expressions/public'; import { DataPublicPluginSetup } from '../../../../src/plugins/data/public'; @@ -60,6 +64,7 @@ export type CanvasStart = void; /** @internal */ export class CanvasPlugin implements Plugin { + private appUpdater = new BehaviorSubject(() => ({})); // TODO: Do we want to completely move canvas_plugin_src into it's own plugin? private srcPlugin = new CanvasSrcPlugin(); @@ -68,12 +73,21 @@ export class CanvasPlugin this.srcPlugin.setup(core, { canvas: canvasApi }); + // Set the nav link to the last saved url if we have one in storage + const lastUrl = getSessionStorage().get(SESSIONSTORAGE_LASTPATH); + if (lastUrl) { + this.appUpdater.next(() => ({ + defaultPath: `#${lastUrl}`, + })); + } + core.application.register({ category: DEFAULT_APP_CATEGORIES.kibana, id: 'canvas', title: 'Canvas', euiIconType: 'canvasApp', order: 3000, + updater$: this.appUpdater, mount: async (params: AppMountParameters) => { // Load application bundle const { renderApp, initializeCanvas, teardownCanvas } = await import('./application'); @@ -81,7 +95,14 @@ export class CanvasPlugin // Get start services const [coreStart, depsStart] = await core.getStartServices(); - const canvasStore = await initializeCanvas(core, coreStart, plugins, depsStart, registries); + const canvasStore = await initializeCanvas( + core, + coreStart, + plugins, + depsStart, + registries, + this.appUpdater + ); const unmount = renderApp(coreStart, depsStart, params, canvasStore); diff --git a/x-pack/plugins/canvas/public/services/index.ts b/x-pack/plugins/canvas/public/services/index.ts index abc46beaa3e64..42176f953c331 100644 --- a/x-pack/plugins/canvas/public/services/index.ts +++ b/x-pack/plugins/canvas/public/services/index.ts @@ -4,16 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, CoreStart } from '../../../../../src/core/public'; +import { BehaviorSubject } from 'rxjs'; +import { CoreSetup, CoreStart, AppUpdater } from '../../../../../src/core/public'; import { CanvasSetupDeps, CanvasStartDeps } from '../plugin'; import { notifyServiceFactory } from './notify'; import { platformServiceFactory } from './platform'; +import { navLinkServiceFactory } from './nav_link'; export type CanvasServiceFactory = ( coreSetup: CoreSetup, coreStart: CoreStart, canvasSetupPlugins: CanvasSetupDeps, - canvasStartPlugins: CanvasStartDeps + canvasStartPlugins: CanvasStartDeps, + appUpdater: BehaviorSubject ) => Service; class CanvasServiceProvider { @@ -28,9 +31,16 @@ class CanvasServiceProvider { coreSetup: CoreSetup, coreStart: CoreStart, canvasSetupPlugins: CanvasSetupDeps, - canvasStartPlugins: CanvasStartDeps + canvasStartPlugins: CanvasStartDeps, + appUpdater: BehaviorSubject ) { - this.service = this.factory(coreSetup, coreStart, canvasSetupPlugins, canvasStartPlugins); + this.service = this.factory( + coreSetup, + coreStart, + canvasSetupPlugins, + canvasStartPlugins, + appUpdater + ); } getService(): Service { @@ -51,20 +61,24 @@ export type ServiceFromProvider

= P extends CanvasServiceProvider ? export const services = { notify: new CanvasServiceProvider(notifyServiceFactory), platform: new CanvasServiceProvider(platformServiceFactory), + navLink: new CanvasServiceProvider(navLinkServiceFactory), }; export interface CanvasServices { notify: ServiceFromProvider; + platform: ServiceFromProvider; + navLink: ServiceFromProvider; } export const startServices = ( coreSetup: CoreSetup, coreStart: CoreStart, canvasSetupPlugins: CanvasSetupDeps, - canvasStartPlugins: CanvasStartDeps + canvasStartPlugins: CanvasStartDeps, + appUpdater: BehaviorSubject ) => { Object.entries(services).forEach(([key, provider]) => - provider.start(coreSetup, coreStart, canvasSetupPlugins, canvasStartPlugins) + provider.start(coreSetup, coreStart, canvasSetupPlugins, canvasStartPlugins, appUpdater) ); }; @@ -72,4 +86,8 @@ export const stopServices = () => { Object.entries(services).forEach(([key, provider]) => provider.stop()); }; -export const { notify: notifyService, platform: platformService } = services; +export const { + notify: notifyService, + platform: platformService, + navLink: navLinkService, +} = services; diff --git a/x-pack/plugins/canvas/public/services/nav_link.ts b/x-pack/plugins/canvas/public/services/nav_link.ts new file mode 100644 index 0000000000000..5061498458006 --- /dev/null +++ b/x-pack/plugins/canvas/public/services/nav_link.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CanvasServiceFactory } from '.'; +import { SESSIONSTORAGE_LASTPATH } from '../../common/lib/constants'; +import { getSessionStorage } from '../lib/storage'; + +interface NavLinkService { + updatePath: (path: string) => void; +} + +export const navLinkServiceFactory: CanvasServiceFactory = ( + coreSetup, + coreStart, + setupPlugins, + startPlugins, + appUpdater +) => { + return { + updatePath: (path: string) => { + appUpdater.next(() => ({ + defaultPath: `#${path}`, + })); + + getSessionStorage().set(SESSIONSTORAGE_LASTPATH, path); + }, + }; +};