From 211622234fc8f9383f18ba1940e2538b64ac137e Mon Sep 17 00:00:00 2001 From: abbyhu2000 Date: Wed, 26 Oct 2022 22:44:19 +0000 Subject: [PATCH 1/5] global data persistence for vis builder Persist filters, time range, time refresh interval for vis builder when we refresh or navigate to other apps such as dashboard, discover, timeline and visualize Signed-off-by: abbyhu2000 --- CHANGELOG.md | 2 +- .../vis_builder/public/application/app.tsx | 24 ++++- .../public/application/components/top_nav.tsx | 5 ++ .../application/utils/get_top_nav_config.tsx | 16 ++-- .../utils/state_management/metadata_slice.ts | 7 +- .../utils/state_management/store.ts | 7 +- src/plugins/vis_builder/public/plugin.test.ts | 1 + src/plugins/vis_builder/public/plugin.ts | 89 +++++++++++++++++-- .../vis_builder/public/plugin_services.ts | 4 + src/plugins/vis_builder/public/types.ts | 8 ++ 10 files changed, 145 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7be07729e4d..bf9bda62aa4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Vis Builder] Fix empty workspace animation does not work in firefox ([#2853](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2853)) - Bumped `del` version to fix MacOS race condition ([#2847](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2873)) - [Build] Fixed "Last Access Time" not being set by `scanCopy` on Windows ([#2964](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2964)) - +- [Vis Builder] global data persistence for vis builder #2896 ([#2896](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2896)) ### 🚞 Infrastructure - Add CHANGELOG.md and related workflows ([#2414](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2414)) diff --git a/src/plugins/vis_builder/public/application/app.tsx b/src/plugins/vis_builder/public/application/app.tsx index bd5f2be1feda..2bdc2b1c631b 100644 --- a/src/plugins/vis_builder/public/application/app.tsx +++ b/src/plugins/vis_builder/public/application/app.tsx @@ -3,17 +3,39 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; +import React, { useEffect } from 'react'; import { I18nProvider } from '@osd/i18n/react'; import { EuiPage, EuiResizableContainer } from '@elastic/eui'; +import { useLocation } from 'react-router-dom'; import { DragDropProvider } from './utils/drag_drop/drag_drop_context'; import { LeftNav } from './components/left_nav'; import { TopNav } from './components/top_nav'; import { Workspace } from './components/workspace'; import './app.scss'; import { RightNav } from './components/right_nav'; +import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public'; +import { VisBuilderServices } from '../types'; +import { syncQueryStateWithUrl } from '../../../data/public'; export const VisBuilderApp = () => { + const { + services: { + data: { query }, + osdUrlStateStorage, + }, + } = useOpenSearchDashboards(); + const { pathname } = useLocation(); + + useEffect(() => { + // syncs `_g` portion of url with query services + const { stop } = syncQueryStateWithUrl(query, osdUrlStateStorage); + + return () => stop(); + + // this effect should re-run when pathname is changed to preserve querystring part, + // so the global state is always preserved + }, [query, osdUrlStateStorage, pathname]); + // Render the application DOM. return ( diff --git a/src/plugins/vis_builder/public/application/components/top_nav.tsx b/src/plugins/vis_builder/public/application/components/top_nav.tsx index 62d3bb78cc52..fe9b735b5acf 100644 --- a/src/plugins/vis_builder/public/application/components/top_nav.tsx +++ b/src/plugins/vis_builder/public/application/components/top_nav.tsx @@ -36,6 +36,9 @@ export const TopNav = () => { const savedVisBuilderVis = useSavedVisBuilderVis(visualizationIdFromUrl); const { selected: indexPattern } = useIndexPatterns(); const [config, setConfig] = useState(); + const originatingApp = useTypedSelector((state) => { + return state.metadata.originatingApp; + }); useEffect(() => { const getConfig = () => { @@ -47,6 +50,7 @@ export const TopNav = () => { savedVisBuilderVis: saveStateToSavedObject(savedVisBuilderVis, rootState, indexPattern), saveDisabledReason, dispatch, + originatingApp, }, services ); @@ -61,6 +65,7 @@ export const TopNav = () => { saveDisabledReason, dispatch, indexPattern, + originatingApp, ]); // reset validity before component destroyed diff --git a/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx b/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx index c88bb13f3cb3..0d1ca409bcca 100644 --- a/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx @@ -46,22 +46,24 @@ export interface TopNavConfigParams { savedVisBuilderVis: VisBuilderVisSavedObject; saveDisabledReason?: string; dispatch: AppDispatch; + originatingApp: string | undefined; } export const getTopNavConfig = ( - { visualizationIdFromUrl, savedVisBuilderVis, saveDisabledReason, dispatch }: TopNavConfigParams, + { + visualizationIdFromUrl, + savedVisBuilderVis, + saveDisabledReason, + dispatch, + originatingApp, + }: TopNavConfigParams, services: VisBuilderServices ) => { const { i18n: { Context: I18nContext }, embeddable, - scopedHistory, } = services; - const { originatingApp, embeddableId } = - embeddable - .getStateTransfer(scopedHistory) - .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'input'] }) || {}; const stateTransfer = embeddable.getStateTransfer(); const topNavConfig: TopNavMenuData[] = [ @@ -105,7 +107,7 @@ export const getTopNavConfig = ( showSaveModal(saveModal, I18nContext); }, }, - ...(originatingApp && ((savedVisBuilderVis && savedVisBuilderVis.id) || embeddableId) + ...(originatingApp && savedVisBuilderVis && savedVisBuilderVis.id ? [ { id: 'saveAndReturn', diff --git a/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts b/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts index 8cc71804f12e..c7b6a2353b47 100644 --- a/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts +++ b/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts @@ -21,6 +21,7 @@ export interface MetadataState { }; state: EditorState; }; + originatingApp: string | undefined; } const initialState: MetadataState = { @@ -28,6 +29,7 @@ const initialState: MetadataState = { validity: {}, state: 'loading', }, + originatingApp: undefined, }; export const getPreloadedState = async ({ @@ -50,6 +52,9 @@ export const slice = createSlice({ setEditorState: (state, action: PayloadAction<{ state: EditorState }>) => { state.editor.state = action.payload.state; }, + setOriginatingApp: (state, action: PayloadAction<{ state: string | undefined }>) => { + state.originatingApp = action.payload.state; + }, setState: (_state, action: PayloadAction) => { return action.payload; }, @@ -57,4 +62,4 @@ export const slice = createSlice({ }); export const { reducer } = slice; -export const { setValidity, setEditorState, setState } = slice.actions; +export const { setValidity, setEditorState, setOriginatingApp, setState } = slice.actions; diff --git a/src/plugins/vis_builder/public/application/utils/state_management/store.ts b/src/plugins/vis_builder/public/application/utils/state_management/store.ts index f02fc5e946dd..ff54c0dbb94f 100644 --- a/src/plugins/vis_builder/public/application/utils/state_management/store.ts +++ b/src/plugins/vis_builder/public/application/utils/state_management/store.ts @@ -6,7 +6,7 @@ import { combineReducers, configureStore, PreloadedState } from '@reduxjs/toolkit'; import { reducer as styleReducer } from './style_slice'; import { reducer as visualizationReducer } from './visualization_slice'; -import { reducer as metadataReducer } from './metadata_slice'; +import { reducer as metadataReducer, setOriginatingApp } from './metadata_slice'; import { VisBuilderServices } from '../../..'; import { getPreloadedState } from './preload'; import { setEditorState } from './metadata_slice'; @@ -25,6 +25,10 @@ export const configurePreloadedStore = (preloadedState: PreloadedState { + const { originatingApp } = + services.embeddable + .getStateTransfer(services.scopedHistory) + .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'input'] }) || {}; const preloadedState = await getPreloadedState(services); const store = configurePreloadedStore(preloadedState); @@ -34,6 +38,7 @@ export const getPreloadedStore = async (services: VisBuilderServices) => { style: styleState, }; let previousMetadata = metadataState; + store.dispatch(setOriginatingApp({ state: originatingApp })); // Listen to changes const handleChange = () => { diff --git a/src/plugins/vis_builder/public/plugin.test.ts b/src/plugins/vis_builder/public/plugin.test.ts index f5fac728420b..35e17865649a 100644 --- a/src/plugins/vis_builder/public/plugin.test.ts +++ b/src/plugins/vis_builder/public/plugin.test.ts @@ -28,6 +28,7 @@ describe('VisBuilderPlugin', () => { const setupDeps = { visualizations: visualizationsPluginMock.createSetupContract(), embeddable: embeddablePluginMock.createSetupContract(), + data: dataPluginMock.createSetupContract(), }; const setup = plugin.setup(coreSetup, setupDeps); diff --git a/src/plugins/vis_builder/public/plugin.ts b/src/plugins/vis_builder/public/plugin.ts index 8e90a04784cd..b8a6f886237b 100644 --- a/src/plugins/vis_builder/public/plugin.ts +++ b/src/plugins/vis_builder/public/plugin.ts @@ -4,13 +4,17 @@ */ import { i18n } from '@osd/i18n'; +import { BehaviorSubject } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; import { AppMountParameters, AppNavLinkStatus, + AppUpdater, CoreSetup, CoreStart, Plugin, PluginInitializerContext, + ScopedHistory, } from '../../../core/public'; import { VisBuilderPluginSetupDependencies, @@ -41,10 +45,17 @@ import { setUISettings, setTypeService, setReactExpressionRenderer, + setQueryService, } from './plugin_services'; import { createSavedVisBuilderLoader } from './saved_visualizations'; import { registerDefaultTypes } from './visualizations'; import { ConfigSchema } from '../config'; +import { + createOsdUrlStateStorage, + createOsdUrlTracker, + withNotifyOnErrors, +} from '../../opensearch_dashboards_utils/public'; +import { opensearchFilters } from '../../data/public'; export class VisBuilderPlugin implements @@ -55,13 +66,50 @@ export class VisBuilderPlugin VisBuilderPluginStartDependencies > { private typeService = new TypeService(); + private appStateUpdater = new BehaviorSubject(() => ({})); + private stopUrlTracking: (() => void) | undefined = undefined; + private currentHistory: ScopedHistory | undefined = undefined; constructor(public initializerContext: PluginInitializerContext) {} public setup( core: CoreSetup, - { embeddable, visualizations }: VisBuilderPluginSetupDependencies + { embeddable, visualizations, data: dataSetup }: VisBuilderPluginSetupDependencies ) { + const { + appMounted, + appUnMounted, + stop: stopUrlTracker, + setActiveUrl, + restorePreviousUrl, + } = createOsdUrlTracker({ + baseUrl: core.http.basePath.prepend('/app/vis-builder'), + defaultSubUrl: '#/', + storageKey: `lastUrl:${core.http.basePath.get()}:vis-builder`, + navLinkUpdater$: this.appStateUpdater, + toastNotifications: core.notifications.toasts, + stateParams: [ + { + osdUrlKey: '_g', + stateUpdate$: dataSetup.query.state$.pipe( + filter( + ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) + ), + map(({ state }) => ({ + ...state, + filters: state.filters?.filter(opensearchFilters.isFilterPinned), + })) + ), + }, + ], + getHistory: () => { + return this.currentHistory!; + }, + }); + this.stopUrlTracking = () => { + stopUrlTracker(); + }; + const typeService = this.typeService; registerDefaultTypes(typeService.setup()); @@ -70,13 +118,15 @@ export class VisBuilderPlugin id: PLUGIN_ID, title: PLUGIN_NAME, navLinkStatus: AppNavLinkStatus.hidden, - async mount(params: AppMountParameters) { + defaultPath: '#/', + mount: async (params: AppMountParameters) => { // Load application bundle const { renderApp } = await import('./application'); // Get start services as specified in opensearch_dashboards.json const [coreStart, pluginsStart, selfStart] = await core.getStartServices(); const { data, savedObjects, navigation, expressions } = pluginsStart; + this.currentHistory = params.history; // make sure the index pattern list is up to date data.indexPatterns.clearCache(); @@ -85,28 +135,48 @@ export class VisBuilderPlugin // TODO: Add the redirect await pluginsStart.data.indexPatterns.ensureDefaultIndexPattern(); - // Register Default Visualizations + appMounted(); + // dispatch synthetic hash change event to update hash history objects + // this is necessary because hash updates triggered by using popState won't trigger this event naturally. + const unlistenParentHistory = params.history.listen(() => { + window.dispatchEvent(new HashChangeEvent('hashchange')); + }); + + // Register Default Visualizations const services: VisBuilderServices = { ...coreStart, + scopedHistory: this.currentHistory, + history: params.history, + osdUrlStateStorage: createOsdUrlStateStorage({ + history: params.history, + useHash: coreStart.uiSettings.get('state:storeInSessionStorage'), + ...withNotifyOnErrors(coreStart.notifications.toasts), + }), toastNotifications: coreStart.notifications.toasts, data, savedObjectsPublic: savedObjects, navigation, expressions, - history: params.history, setHeaderActionMenu: params.setHeaderActionMenu, types: typeService.start(), savedVisBuilderLoader: selfStart.savedVisBuilderLoader, embeddable: pluginsStart.embeddable, - scopedHistory: params.history, + setActiveUrl, + restorePreviousUrl, + dashboard: pluginsStart.dashboard, }; // Instantiate the store const store = await getPreloadedStore(services); + const unmount = renderApp(params, services, store); // Render the application - return renderApp(params, services, store); + return () => { + unlistenParentHistory(); + unmount(); + appUnMounted(); + }; }, }); @@ -176,6 +246,7 @@ export class VisBuilderPlugin setTimeFilter(data.query.timefilter.timefilter); setTypeService(typeService); setUISettings(core.uiSettings); + setQueryService(data.query); return { ...typeService, @@ -183,5 +254,9 @@ export class VisBuilderPlugin }; } - public stop() {} + public stop() { + if (this.stopUrlTracking) { + this.stopUrlTracking(); + } + } } diff --git a/src/plugins/vis_builder/public/plugin_services.ts b/src/plugins/vis_builder/public/plugin_services.ts index f979f3a22b11..c5583e3c5e43 100644 --- a/src/plugins/vis_builder/public/plugin_services.ts +++ b/src/plugins/vis_builder/public/plugin_services.ts @@ -37,3 +37,7 @@ export const [getTimeFilter, setTimeFilter] = createGetterSetter('TypeService'); export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); + +export const [getQueryService, setQueryService] = createGetterSetter< + DataPublicPluginStart['query'] +>('Query'); diff --git a/src/plugins/vis_builder/public/types.ts b/src/plugins/vis_builder/public/types.ts index 131c9cc1f6bb..5780702be9cf 100644 --- a/src/plugins/vis_builder/public/types.ts +++ b/src/plugins/vis_builder/public/types.ts @@ -14,6 +14,9 @@ import { DataPublicPluginStart } from '../../data/public'; import { TypeServiceSetup, TypeServiceStart } from './services/type_service'; import { SavedObjectLoader } from '../../saved_objects/public'; import { AppMountParameters, CoreStart, ToastsStart, ScopedHistory } from '../../../core/public'; +import { IOsdUrlStateStorage } from '../../opensearch_dashboards_utils/public'; +import { UrlForwardingSetup } from '../../url_forwarding/public'; +import { DataPublicPluginSetup } from '../../data/public'; export type VisBuilderSetup = TypeServiceSetup; export interface VisBuilderStart extends TypeServiceStart { @@ -23,6 +26,7 @@ export interface VisBuilderStart extends TypeServiceStart { export interface VisBuilderPluginSetupDependencies { embeddable: EmbeddableSetup; visualizations: VisualizationsSetup; + data: DataPublicPluginSetup; } export interface VisBuilderPluginStartDependencies { embeddable: EmbeddableStart; @@ -45,6 +49,10 @@ export interface VisBuilderServices extends CoreStart { history: History; embeddable: EmbeddableStart; scopedHistory: ScopedHistory; + osdUrlStateStorage: IOsdUrlStateStorage; + setActiveUrl: (newUrl: string) => void; + restorePreviousUrl: () => void; + dashboard: DashboardStart; } export interface ISavedVis { From efb7f22898a0a4d13ae8f14181e565dc3993ed60 Mon Sep 17 00:00:00 2001 From: abbyhu2000 Date: Mon, 28 Nov 2022 23:12:36 +0000 Subject: [PATCH 2/5] move createOsdUrlTracker in mount function Signed-off-by: abbyhu2000 --- .../utils/state_management/metadata_slice.ts | 4 +- src/plugins/vis_builder/public/plugin.ts | 66 +++++++++---------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts b/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts index c7b6a2353b47..fc73f22b173d 100644 --- a/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts +++ b/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts @@ -21,7 +21,7 @@ export interface MetadataState { }; state: EditorState; }; - originatingApp: string | undefined; + originatingApp?: string; } const initialState: MetadataState = { @@ -52,7 +52,7 @@ export const slice = createSlice({ setEditorState: (state, action: PayloadAction<{ state: EditorState }>) => { state.editor.state = action.payload.state; }, - setOriginatingApp: (state, action: PayloadAction<{ state: string | undefined }>) => { + setOriginatingApp: (state, action: PayloadAction<{ state?: string }>) => { state.originatingApp = action.payload.state; }, setState: (_state, action: PayloadAction) => { diff --git a/src/plugins/vis_builder/public/plugin.ts b/src/plugins/vis_builder/public/plugin.ts index b8a6f886237b..c4b01a8e7fc4 100644 --- a/src/plugins/vis_builder/public/plugin.ts +++ b/src/plugins/vis_builder/public/plugin.ts @@ -74,42 +74,9 @@ export class VisBuilderPlugin public setup( core: CoreSetup, - { embeddable, visualizations, data: dataSetup }: VisBuilderPluginSetupDependencies + { embeddable, visualizations }: VisBuilderPluginSetupDependencies ) { - const { - appMounted, - appUnMounted, - stop: stopUrlTracker, - setActiveUrl, - restorePreviousUrl, - } = createOsdUrlTracker({ - baseUrl: core.http.basePath.prepend('/app/vis-builder'), - defaultSubUrl: '#/', - storageKey: `lastUrl:${core.http.basePath.get()}:vis-builder`, - navLinkUpdater$: this.appStateUpdater, - toastNotifications: core.notifications.toasts, - stateParams: [ - { - osdUrlKey: '_g', - stateUpdate$: dataSetup.query.state$.pipe( - filter( - ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) - ), - map(({ state }) => ({ - ...state, - filters: state.filters?.filter(opensearchFilters.isFilterPinned), - })) - ), - }, - ], - getHistory: () => { - return this.currentHistory!; - }, - }); - this.stopUrlTracking = () => { - stopUrlTracker(); - }; - + // Register Default Visualizations const typeService = this.typeService; registerDefaultTypes(typeService.setup()); @@ -128,6 +95,35 @@ export class VisBuilderPlugin const { data, savedObjects, navigation, expressions } = pluginsStart; this.currentHistory = params.history; + const { appMounted, appUnMounted, stop: stopUrlTracker } = createOsdUrlTracker({ + baseUrl: core.http.basePath.prepend('/app/vis-builder'), + defaultSubUrl: '#/', + storageKey: `lastUrl:${core.http.basePath.get()}:vis-builder`, + navLinkUpdater$: this.appStateUpdater, + toastNotifications: core.notifications.toasts, + stateParams: [ + { + osdUrlKey: '_g', + stateUpdate$: data.query.state$.pipe( + filter( + ({ changes }) => + !!(changes.globalFilters || changes.time || changes.refreshInterval) + ), + map(({ state }) => ({ + ...state, + filters: state.filters?.filter(opensearchFilters.isFilterPinned), + })) + ), + }, + ], + getHistory: () => { + return this.currentHistory!; + }, + }); + this.stopUrlTracking = () => { + stopUrlTracker(); + }; + // make sure the index pattern list is up to date data.indexPatterns.clearCache(); // make sure a default index pattern exists From 616c48eba3b9130abc02711f98a88e1f6518df38 Mon Sep 17 00:00:00 2001 From: abbyhu2000 Date: Wed, 30 Nov 2022 22:49:08 +0000 Subject: [PATCH 3/5] Move osdUrlTracker out of mount Move osdUrlTracker to its original location, and replace data dependency with pluginsStart.data Signed-off-by: abbyhu2000 --- src/plugins/vis_builder/public/plugin.ts | 69 ++++++++++++------------ src/plugins/vis_builder/public/types.ts | 2 - 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/plugins/vis_builder/public/plugin.ts b/src/plugins/vis_builder/public/plugin.ts index c4b01a8e7fc4..15a3cc74e6cf 100644 --- a/src/plugins/vis_builder/public/plugin.ts +++ b/src/plugins/vis_builder/public/plugin.ts @@ -74,8 +74,36 @@ export class VisBuilderPlugin public setup( core: CoreSetup, - { embeddable, visualizations }: VisBuilderPluginSetupDependencies + { embeddable, visualizations, data }: VisBuilderPluginSetupDependencies ) { + const { appMounted, appUnMounted, stop: stopUrlTracker } = createOsdUrlTracker({ + baseUrl: core.http.basePath.prepend('/app/vis-builder'), + defaultSubUrl: '#/', + storageKey: `lastUrl:${core.http.basePath.get()}:vis-builder`, + navLinkUpdater$: this.appStateUpdater, + toastNotifications: core.notifications.toasts, + stateParams: [ + { + osdUrlKey: '_g', + stateUpdate$: data.query.state$.pipe( + filter( + ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) + ), + map(({ state }) => ({ + ...state, + filters: state.filters?.filter(opensearchFilters.isFilterPinned), + })) + ), + }, + ], + getHistory: () => { + return this.currentHistory!; + }, + }); + this.stopUrlTracking = () => { + stopUrlTracker(); + }; + // Register Default Visualizations const typeService = this.typeService; registerDefaultTypes(typeService.setup()); @@ -92,40 +120,11 @@ export class VisBuilderPlugin // Get start services as specified in opensearch_dashboards.json const [coreStart, pluginsStart, selfStart] = await core.getStartServices(); - const { data, savedObjects, navigation, expressions } = pluginsStart; + const { savedObjects, navigation, expressions } = pluginsStart; this.currentHistory = params.history; - const { appMounted, appUnMounted, stop: stopUrlTracker } = createOsdUrlTracker({ - baseUrl: core.http.basePath.prepend('/app/vis-builder'), - defaultSubUrl: '#/', - storageKey: `lastUrl:${core.http.basePath.get()}:vis-builder`, - navLinkUpdater$: this.appStateUpdater, - toastNotifications: core.notifications.toasts, - stateParams: [ - { - osdUrlKey: '_g', - stateUpdate$: data.query.state$.pipe( - filter( - ({ changes }) => - !!(changes.globalFilters || changes.time || changes.refreshInterval) - ), - map(({ state }) => ({ - ...state, - filters: state.filters?.filter(opensearchFilters.isFilterPinned), - })) - ), - }, - ], - getHistory: () => { - return this.currentHistory!; - }, - }); - this.stopUrlTracking = () => { - stopUrlTracker(); - }; - // make sure the index pattern list is up to date - data.indexPatterns.clearCache(); + pluginsStart.data.indexPatterns.clearCache(); // make sure a default index pattern exists // if not, the page will be redirected to management and visualize won't be rendered // TODO: Add the redirect @@ -150,7 +149,7 @@ export class VisBuilderPlugin ...withNotifyOnErrors(coreStart.notifications.toasts), }), toastNotifications: coreStart.notifications.toasts, - data, + data: pluginsStart.data, savedObjectsPublic: savedObjects, navigation, expressions, @@ -158,8 +157,6 @@ export class VisBuilderPlugin types: typeService.start(), savedVisBuilderLoader: selfStart.savedVisBuilderLoader, embeddable: pluginsStart.embeddable, - setActiveUrl, - restorePreviousUrl, dashboard: pluginsStart.dashboard, }; @@ -220,7 +217,7 @@ export class VisBuilderPlugin public start( core: CoreStart, - { data, expressions }: VisBuilderPluginStartDependencies + { expressions, data }: VisBuilderPluginStartDependencies ): VisBuilderStart { const typeService = this.typeService.start(); diff --git a/src/plugins/vis_builder/public/types.ts b/src/plugins/vis_builder/public/types.ts index 5780702be9cf..e73e279e05a9 100644 --- a/src/plugins/vis_builder/public/types.ts +++ b/src/plugins/vis_builder/public/types.ts @@ -50,8 +50,6 @@ export interface VisBuilderServices extends CoreStart { embeddable: EmbeddableStart; scopedHistory: ScopedHistory; osdUrlStateStorage: IOsdUrlStateStorage; - setActiveUrl: (newUrl: string) => void; - restorePreviousUrl: () => void; dashboard: DashboardStart; } From a1466744f50231fbb57c368c853e751c992489b2 Mon Sep 17 00:00:00 2001 From: abbyhu2000 Date: Thu, 1 Dec 2022 20:28:42 +0000 Subject: [PATCH 4/5] Change changelog Signed-off-by: abbyhu2000 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf9bda62aa4b..cc0e3ea72b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Vis Builder] Fix empty workspace animation does not work in firefox ([#2853](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2853)) - Bumped `del` version to fix MacOS race condition ([#2847](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2873)) - [Build] Fixed "Last Access Time" not being set by `scanCopy` on Windows ([#2964](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2964)) -- [Vis Builder] global data persistence for vis builder #2896 ([#2896](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2896)) +- [Vis Builder] Add global data persistence for vis builder #2896 ([#2896](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2896)) + ### 🚞 Infrastructure - Add CHANGELOG.md and related workflows ([#2414](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2414)) From e37233538ba4e84684fbaaafec510d63aa9fbad3 Mon Sep 17 00:00:00 2001 From: abbyhu2000 Date: Fri, 2 Dec 2022 19:13:48 +0000 Subject: [PATCH 5/5] resolve comments Signed-off-by: abbyhu2000 --- .../application/utils/get_top_nav_config.tsx | 2 +- .../utils/state_management/metadata_slice.ts | 8 +++++++- .../application/utils/state_management/store.ts | 7 +------ src/plugins/vis_builder/public/plugin.ts | 17 ++++++++--------- src/plugins/vis_builder/public/types.ts | 1 - 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx b/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx index 0d1ca409bcca..9df321822852 100644 --- a/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/vis_builder/public/application/utils/get_top_nav_config.tsx @@ -46,7 +46,7 @@ export interface TopNavConfigParams { savedVisBuilderVis: VisBuilderVisSavedObject; saveDisabledReason?: string; dispatch: AppDispatch; - originatingApp: string | undefined; + originatingApp?: string; } export const getTopNavConfig = ( diff --git a/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts b/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts index fc73f22b173d..c1e23b52823d 100644 --- a/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts +++ b/src/plugins/vis_builder/public/application/utils/state_management/metadata_slice.ts @@ -35,8 +35,14 @@ const initialState: MetadataState = { export const getPreloadedState = async ({ types, data, + embeddable, + scopedHistory, }: VisBuilderServices): Promise => { - const preloadedState = { ...initialState }; + const { originatingApp } = + embeddable + .getStateTransfer(scopedHistory) + .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'input'] }) || {}; + const preloadedState = { ...initialState, originatingApp }; return preloadedState; }; diff --git a/src/plugins/vis_builder/public/application/utils/state_management/store.ts b/src/plugins/vis_builder/public/application/utils/state_management/store.ts index ff54c0dbb94f..f02fc5e946dd 100644 --- a/src/plugins/vis_builder/public/application/utils/state_management/store.ts +++ b/src/plugins/vis_builder/public/application/utils/state_management/store.ts @@ -6,7 +6,7 @@ import { combineReducers, configureStore, PreloadedState } from '@reduxjs/toolkit'; import { reducer as styleReducer } from './style_slice'; import { reducer as visualizationReducer } from './visualization_slice'; -import { reducer as metadataReducer, setOriginatingApp } from './metadata_slice'; +import { reducer as metadataReducer } from './metadata_slice'; import { VisBuilderServices } from '../../..'; import { getPreloadedState } from './preload'; import { setEditorState } from './metadata_slice'; @@ -25,10 +25,6 @@ export const configurePreloadedStore = (preloadedState: PreloadedState { - const { originatingApp } = - services.embeddable - .getStateTransfer(services.scopedHistory) - .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'input'] }) || {}; const preloadedState = await getPreloadedState(services); const store = configurePreloadedStore(preloadedState); @@ -38,7 +34,6 @@ export const getPreloadedStore = async (services: VisBuilderServices) => { style: styleState, }; let previousMetadata = metadataState; - store.dispatch(setOriginatingApp({ state: originatingApp })); // Listen to changes const handleChange = () => { diff --git a/src/plugins/vis_builder/public/plugin.ts b/src/plugins/vis_builder/public/plugin.ts index 15a3cc74e6cf..a619958653b6 100644 --- a/src/plugins/vis_builder/public/plugin.ts +++ b/src/plugins/vis_builder/public/plugin.ts @@ -67,8 +67,8 @@ export class VisBuilderPlugin > { private typeService = new TypeService(); private appStateUpdater = new BehaviorSubject(() => ({})); - private stopUrlTracking: (() => void) | undefined = undefined; - private currentHistory: ScopedHistory | undefined = undefined; + private stopUrlTracking?: () => void; + private currentHistory?: ScopedHistory; constructor(public initializerContext: PluginInitializerContext) {} @@ -77,9 +77,9 @@ export class VisBuilderPlugin { embeddable, visualizations, data }: VisBuilderPluginSetupDependencies ) { const { appMounted, appUnMounted, stop: stopUrlTracker } = createOsdUrlTracker({ - baseUrl: core.http.basePath.prepend('/app/vis-builder'), + baseUrl: core.http.basePath.prepend(`/app/${PLUGIN_ID}`), defaultSubUrl: '#/', - storageKey: `lastUrl:${core.http.basePath.get()}:vis-builder`, + storageKey: `lastUrl:${core.http.basePath.get()}:${PLUGIN_ID}`, navLinkUpdater$: this.appStateUpdater, toastNotifications: core.notifications.toasts, stateParams: [ @@ -134,17 +134,16 @@ export class VisBuilderPlugin // dispatch synthetic hash change event to update hash history objects // this is necessary because hash updates triggered by using popState won't trigger this event naturally. - const unlistenParentHistory = params.history.listen(() => { + const unlistenParentHistory = this.currentHistory.listen(() => { window.dispatchEvent(new HashChangeEvent('hashchange')); }); - // Register Default Visualizations const services: VisBuilderServices = { ...coreStart, scopedHistory: this.currentHistory, - history: params.history, + history: this.currentHistory, osdUrlStateStorage: createOsdUrlStateStorage({ - history: params.history, + history: this.currentHistory, useHash: coreStart.uiSettings.get('state:storeInSessionStorage'), ...withNotifyOnErrors(coreStart.notifications.toasts), }), @@ -162,8 +161,8 @@ export class VisBuilderPlugin // Instantiate the store const store = await getPreloadedStore(services); - const unmount = renderApp(params, services, store); + // Render the application return () => { unlistenParentHistory(); diff --git a/src/plugins/vis_builder/public/types.ts b/src/plugins/vis_builder/public/types.ts index e73e279e05a9..2d323a13b213 100644 --- a/src/plugins/vis_builder/public/types.ts +++ b/src/plugins/vis_builder/public/types.ts @@ -15,7 +15,6 @@ import { TypeServiceSetup, TypeServiceStart } from './services/type_service'; import { SavedObjectLoader } from '../../saved_objects/public'; import { AppMountParameters, CoreStart, ToastsStart, ScopedHistory } from '../../../core/public'; import { IOsdUrlStateStorage } from '../../opensearch_dashboards_utils/public'; -import { UrlForwardingSetup } from '../../url_forwarding/public'; import { DataPublicPluginSetup } from '../../data/public'; export type VisBuilderSetup = TypeServiceSetup;