From f71e452c6056e9254379360f56fb4440702048b7 Mon Sep 17 00:00:00 2001 From: abbyhu2000 Date: Fri, 16 Sep 2022 15:47:25 +0000 Subject: [PATCH 1/4] Create a new wizard directly on a dashboard After selecting a dashboard, create a new wizard directly from the dashboard and have the option to add to that dashboard. Signed-off-by: abbyhu2000 --- .../public/application/components/top_nav.tsx | 6 ++++- .../application/utils/get_top_nav_config.tsx | 25 +++++++++++++++++++ src/plugins/wizard/public/plugin.ts | 2 ++ src/plugins/wizard/public/types.ts | 4 ++- 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/plugins/wizard/public/application/components/top_nav.tsx b/src/plugins/wizard/public/application/components/top_nav.tsx index d816a72d0aae..91646b2952b5 100644 --- a/src/plugins/wizard/public/application/components/top_nav.tsx +++ b/src/plugins/wizard/public/application/components/top_nav.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; import { useUnmount } from 'react-use'; import { PLUGIN_ID } from '../../../common'; @@ -40,6 +40,9 @@ export const TopNav = () => { return getTopNavConfig( { + originatingApp, + setOriginatingApp, + stateTransfer, visualizationIdFromUrl, savedWizardVis: saveStateToSavedObject(savedWizardVis, rootState, indexPattern), saveDisabledReason, @@ -57,6 +60,7 @@ export const TopNav = () => { services, ]); + // reset validity before component destroyed useUnmount(() => { dispatch(setEditorState({ state: 'loading' })); diff --git a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx index cad63d62c9a7..42a17933d5ea 100644 --- a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx @@ -41,11 +41,15 @@ import { WizardVisSavedObject } from '../../types'; import { AppDispatch } from './state_management'; import { EDIT_PATH } from '../../../common'; import { setEditorState } from './state_management/metadata_slice'; +import { EmbeddableStateTransfer } from '../../../../embeddable/public'; interface TopNavConfigParams { visualizationIdFromUrl: string; savedWizardVis: WizardVisSavedObject; saveDisabledReason?: string; dispatch: AppDispatch; + originatingApp?: string; + setOriginatingApp?: (originatingApp: string | undefined) => void; + stateTransfer: EmbeddableStateTransfer; } export const getTopNavConfig = ( @@ -79,6 +83,7 @@ export const getTopNavConfig = ( if (!savedWizardVis) { return; } + const newlyCreated = !Boolean(savedWizardVis.id) || savedWizardVis.copyOnSave; const currentTitle = savedWizardVis.title; savedWizardVis.title = newTitle; savedWizardVis.description = newDescription; @@ -106,6 +111,24 @@ export const getTopNavConfig = ( 'data-test-subj': 'saveVisualizationSuccess', }); + if (originatingApp && returnToOrigin) { + // create or edit wizard directly from a dashboard + if (newlyCreated && stateTransfer) { + // create and add a new wizard to the dashboard + stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { + state: { type: 'wizard', input: { savedObjectId: id } }, + }); + } else { + // edit an existing wizard from the dashboard + application.navigateToApp(originatingApp); + } + } else { + // create wizard from creating visualization page, not related to any dashboard + if (setOriginatingApp && originatingApp && newlyCreated) { + setOriginatingApp(undefined); + } + } + // Update URL if (id !== visualizationIdFromUrl) { history.push({ @@ -148,6 +171,8 @@ export const getTopNavConfig = ( onSave={onSave} objectType={'wizard'} onClose={() => {}} + originatingApp={originatingApp} + getAppNameFromId={stateTransfer.getAppNameFromId} /> ); diff --git a/src/plugins/wizard/public/plugin.ts b/src/plugins/wizard/public/plugin.ts index 5746913bd4ad..7b0e983c4a83 100644 --- a/src/plugins/wizard/public/plugin.ts +++ b/src/plugins/wizard/public/plugin.ts @@ -87,6 +87,8 @@ export class WizardPlugin setHeaderActionMenu: params.setHeaderActionMenu, types: typeService.start(), savedWizardLoader: selfStart.savedWizardLoader, + embeddable: pluginsStart.embeddable, + scopedHistory: params.history, }; // Instantiate the store diff --git a/src/plugins/wizard/public/types.ts b/src/plugins/wizard/public/types.ts index f8371b832bdc..8745a6bfbe18 100644 --- a/src/plugins/wizard/public/types.ts +++ b/src/plugins/wizard/public/types.ts @@ -13,7 +13,7 @@ import { NavigationPublicPluginStart } from '../../navigation/public'; import { DataPublicPluginStart } from '../../data/public'; import { TypeServiceSetup, TypeServiceStart } from './services/type_service'; import { SavedObjectLoader } from '../../saved_objects/public'; -import { AppMountParameters, CoreStart, ToastsStart } from '../../../core/public'; +import { AppMountParameters, CoreStart, ToastsStart, ScopedHistory } from '../../../core/public'; export type WizardSetup = TypeServiceSetup; export interface WizardStart extends TypeServiceStart { @@ -43,6 +43,8 @@ export interface WizardServices extends CoreStart { types: TypeServiceStart; expressions: ExpressionsStart; history: History; + embeddable: EmbeddableStart; + scopedHistory: ScopedHistory; } export interface ISavedVis { From 1d8d921e489965df4e3b83675d7b4fb44ffd96d5 Mon Sep 17 00:00:00 2001 From: abbyhu2000 Date: Tue, 20 Sep 2022 20:00:03 +0000 Subject: [PATCH 2/4] hook change to solve browser warning Signed-off-by: abbyhu2000 --- .../public/application/components/top_nav.tsx | 42 ++++++++----------- .../application/utils/get_top_nav_config.tsx | 16 +++---- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/plugins/wizard/public/application/components/top_nav.tsx b/src/plugins/wizard/public/application/components/top_nav.tsx index 91646b2952b5..173709264f5c 100644 --- a/src/plugins/wizard/public/application/components/top_nav.tsx +++ b/src/plugins/wizard/public/application/components/top_nav.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { useUnmount } from 'react-use'; import { PLUGIN_ID } from '../../../common'; @@ -17,6 +17,7 @@ import { useTypedSelector, useTypedDispatch } from '../utils/state_management'; import { setEditorState } from '../utils/state_management/metadata_slice'; import { useCanSave } from '../utils/use/use_can_save'; import { saveStateToSavedObject } from '../../saved_visualizations/transforms'; +import { TopNavMenuData } from '../../../../navigation/public'; export const TopNav = () => { // id will only be set for the edit route @@ -34,32 +35,25 @@ export const TopNav = () => { const saveDisabledReason = useCanSave(); const savedWizardVis = useSavedWizardVis(visualizationIdFromUrl); const { selected: indexPattern } = useIndexPatterns(); + const [config, setConfig] = useState(); - const config = useMemo(() => { - if (!savedWizardVis || !indexPattern) return; + useEffect(() => { + const getConfig = () => { + if (!savedWizardVis || !indexPattern) return; - return getTopNavConfig( - { - originatingApp, - setOriginatingApp, - stateTransfer, - visualizationIdFromUrl, - savedWizardVis: saveStateToSavedObject(savedWizardVis, rootState, indexPattern), - saveDisabledReason, - dispatch, - }, - services - ); - }, [ - savedWizardVis, - visualizationIdFromUrl, - rootState, - indexPattern, - saveDisabledReason, - dispatch, - services, - ]); + return getTopNavConfig( + { + visualizationIdFromUrl, + savedWizardVis: saveStateToSavedObject(savedWizardVis, rootState, indexPattern), + saveDisabledReason, + dispatch, + }, + services + ); + }; + setConfig(getConfig()); + }, [rootState, savedWizardVis, services, visualizationIdFromUrl, saveDisabledReason, dispatch, indexPattern]); // reset validity before component destroyed useUnmount(() => { diff --git a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx index 42a17933d5ea..99e02cda6f25 100644 --- a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx @@ -41,21 +41,22 @@ import { WizardVisSavedObject } from '../../types'; import { AppDispatch } from './state_management'; import { EDIT_PATH } from '../../../common'; import { setEditorState } from './state_management/metadata_slice'; -import { EmbeddableStateTransfer } from '../../../../embeddable/public'; interface TopNavConfigParams { visualizationIdFromUrl: string; savedWizardVis: WizardVisSavedObject; saveDisabledReason?: string; dispatch: AppDispatch; - originatingApp?: string; - setOriginatingApp?: (originatingApp: string | undefined) => void; - stateTransfer: EmbeddableStateTransfer; } export const getTopNavConfig = ( { visualizationIdFromUrl, savedWizardVis, saveDisabledReason, dispatch }: TopNavConfigParams, - { history, toastNotifications, i18n: { Context: I18nContext } }: WizardServices + { application, history, toastNotifications, i18n: { Context: I18nContext }, embeddable, scopedHistory }: WizardServices ) => { + const { originatingApp: originatingApp } = embeddable + .getStateTransfer(scopedHistory) + .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'input'] }) || {}; + const stateTransfer = embeddable.getStateTransfer(); + const topNavConfig: TopNavMenuData[] = [ { id: 'save', @@ -118,14 +119,15 @@ export const getTopNavConfig = ( stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { state: { type: 'wizard', input: { savedObjectId: id } }, }); + return {id}; } else { // edit an existing wizard from the dashboard application.navigateToApp(originatingApp); } } else { // create wizard from creating visualization page, not related to any dashboard - if (setOriginatingApp && originatingApp && newlyCreated) { - setOriginatingApp(undefined); + if ( originatingApp && newlyCreated) { + //setOriginatingApp(undefined); } } From 15e9d23e6b6e8e60d5bfa77f96e465e435d3048c Mon Sep 17 00:00:00 2001 From: abbyhu2000 Date: Tue, 27 Sep 2022 23:01:39 +0000 Subject: [PATCH 3/4] Move onsave out from getTopNavConfig function and added unit tests Signed-off-by: abbyhu2000 --- config/opensearch_dashboards.yml | 1 + .../utils/get_top_nav_config.test.tsx | 155 +++++++++++++ .../application/utils/get_top_nav_config.tsx | 213 ++++++++++-------- .../wizard/public/application/utils/mocks.ts | 46 ++++ 4 files changed, 317 insertions(+), 98 deletions(-) create mode 100644 src/plugins/wizard/public/application/utils/get_top_nav_config.test.tsx create mode 100644 src/plugins/wizard/public/application/utils/mocks.ts diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index 4f23dc42c050..da2dfb10d9c3 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -232,3 +232,4 @@ #data_source.encryption.wrappingKeyName: 'changeme' #data_source.encryption.wrappingKeyNamespace: 'changeme' #data_source.encryption.wrappingKey: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + diff --git a/src/plugins/wizard/public/application/utils/get_top_nav_config.test.tsx b/src/plugins/wizard/public/application/utils/get_top_nav_config.test.tsx new file mode 100644 index 000000000000..456297d99a59 --- /dev/null +++ b/src/plugins/wizard/public/application/utils/get_top_nav_config.test.tsx @@ -0,0 +1,155 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { WizardServices } from '../../types'; +import { getOnSave } from './get_top_nav_config'; +import { createWizardServicesMock } from './mocks'; + +describe('getOnSave', () => { + let savedWizardVis: any; + let originatingApp: string | undefined; + let visualizationIdFromUrl: string; + let dispatch: any; + let mockServices: jest.Mocked; + let onSaveProps: { + newTitle: string; + newCopyOnSave: boolean; + isTitleDuplicateConfirmed: boolean; + onTitleDuplicate: any; + newDescription: string; + returnToOrigin: boolean; + }; + + beforeEach(() => { + savedWizardVis = { + id: '1', + title: 'save wizard wiz title', + description: '', + visualizationState: '', + styleState: '', + version: 0, + copyOnSave: true, + searchSourceFields: {}, + save: jest.fn().mockReturnValue('1'), + }; + originatingApp = ''; + visualizationIdFromUrl = ''; + dispatch = jest.fn(); + mockServices = createWizardServicesMock(); + + onSaveProps = { + newTitle: 'new title', + newCopyOnSave: false, + isTitleDuplicateConfirmed: false, + onTitleDuplicate: jest.fn(), + newDescription: 'new description', + returnToOrigin: true, + }; + }); + + test('return undefined when savedWizardVis is null', async () => { + savedWizardVis = null; + const onSave = getOnSave( + savedWizardVis, + originatingApp, + visualizationIdFromUrl, + dispatch, + mockServices + ); + const onSaveResult = await onSave(onSaveProps); + + expect(onSaveResult).toBeUndefined(); + }); + + test('savedWizardVis get saved correctly', async () => { + const onSave = getOnSave( + savedWizardVis, + originatingApp, + visualizationIdFromUrl, + dispatch, + mockServices + ); + const onSaveReturn = await onSave(onSaveProps); + expect(savedWizardVis).toMatchInlineSnapshot(` + Object { + "copyOnSave": false, + "description": "new description", + "id": "1", + "save": [MockFunction] { + "calls": Array [ + Array [ + Object { + "confirmOverwrite": false, + "isTitleDuplicateConfirmed": false, + "onTitleDuplicate": [MockFunction], + "returnToOrigin": true, + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": "1", + }, + ], + }, + "searchSourceFields": Object {}, + "styleState": "", + "title": "new title", + "version": 0, + "visualizationState": "", + } + `); + expect(onSaveReturn?.id).toBe('1'); + }); + + test('savedWizardVis does not change title with a null id', async () => { + savedWizardVis.save = jest.fn().mockReturnValue(null); + const onSave = getOnSave( + savedWizardVis, + originatingApp, + visualizationIdFromUrl, + dispatch, + mockServices + ); + const onSaveResult = await onSave(onSaveProps); + expect(savedWizardVis.title).toBe('save wizard wiz title'); + expect(onSaveResult?.id).toBeNull(); + }); + + test('create a new wizard from dashboard', async () => { + savedWizardVis.id = null; + savedWizardVis.save = jest.fn().mockReturnValue('2'); + originatingApp = 'dashboard'; + onSaveProps.returnToOrigin = true; + + const onSave = getOnSave( + savedWizardVis, + originatingApp, + visualizationIdFromUrl, + dispatch, + mockServices + ); + const onSaveResult = await onSave(onSaveProps); + expect(onSaveResult?.id).toBe('2'); + expect(dispatch).toBeCalledTimes(0); + }); + + test('edit an exising wizard from dashboard', async () => { + savedWizardVis.copyOnSave = false; + originatingApp = 'dashboard'; + onSaveProps.returnToOrigin = true; + const onSave = getOnSave( + savedWizardVis, + originatingApp, + visualizationIdFromUrl, + dispatch, + mockServices + ); + const onSaveResult = await onSave(onSaveProps); + expect(onSaveResult?.id).toBe('1'); + expect(mockServices.application.navigateToApp).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx index 99e02cda6f25..529d91f76f86 100644 --- a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx @@ -41,7 +41,7 @@ import { WizardVisSavedObject } from '../../types'; import { AppDispatch } from './state_management'; import { EDIT_PATH } from '../../../common'; import { setEditorState } from './state_management/metadata_slice'; -interface TopNavConfigParams { +export interface TopNavConfigParams { visualizationIdFromUrl: string; savedWizardVis: WizardVisSavedObject; saveDisabledReason?: string; @@ -50,9 +50,16 @@ interface TopNavConfigParams { export const getTopNavConfig = ( { visualizationIdFromUrl, savedWizardVis, saveDisabledReason, dispatch }: TopNavConfigParams, - { application, history, toastNotifications, i18n: { Context: I18nContext }, embeddable, scopedHistory }: WizardServices + services: WizardServices ) => { - const { originatingApp: originatingApp } = embeddable + const { + i18n: { Context: I18nContext }, + embeddable, + scopedHistory, + } = services; + + const { originatingApp: originatingApp } = + embeddable .getStateTransfer(scopedHistory) .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'input'] }) || {}; const stateTransfer = embeddable.getStateTransfer(); @@ -73,104 +80,16 @@ export const getTopNavConfig = ( disableButton: !!saveDisabledReason, tooltip: saveDisabledReason, run: (_anchorElement) => { - const onSave = async ({ - newTitle, - newCopyOnSave, - isTitleDuplicateConfirmed, - onTitleDuplicate, - newDescription, - returnToOrigin, - }: OnSaveProps & { returnToOrigin: boolean }) => { - if (!savedWizardVis) { - return; - } - const newlyCreated = !Boolean(savedWizardVis.id) || savedWizardVis.copyOnSave; - const currentTitle = savedWizardVis.title; - savedWizardVis.title = newTitle; - savedWizardVis.description = newDescription; - savedWizardVis.copyOnSave = newCopyOnSave; - - try { - const id = await savedWizardVis.save({ - confirmOverwrite: false, - isTitleDuplicateConfirmed, - onTitleDuplicate, - returnToOrigin, - }); - - if (id) { - toastNotifications.addSuccess({ - title: i18n.translate( - 'wizard.topNavMenu.saveVisualization.successNotificationText', - { - defaultMessage: `Saved '{visTitle}'`, - values: { - visTitle: savedWizardVis.title, - }, - } - ), - 'data-test-subj': 'saveVisualizationSuccess', - }); - - if (originatingApp && returnToOrigin) { - // create or edit wizard directly from a dashboard - if (newlyCreated && stateTransfer) { - // create and add a new wizard to the dashboard - stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { - state: { type: 'wizard', input: { savedObjectId: id } }, - }); - return {id}; - } else { - // edit an existing wizard from the dashboard - application.navigateToApp(originatingApp); - } - } else { - // create wizard from creating visualization page, not related to any dashboard - if ( originatingApp && newlyCreated) { - //setOriginatingApp(undefined); - } - } - - // Update URL - if (id !== visualizationIdFromUrl) { - history.push({ - ...history.location, - pathname: `${EDIT_PATH}/${id}`, - }); - } - dispatch(setEditorState({ state: 'clean' })); - } else { - // reset title if save not successful - savedWizardVis.title = currentTitle; - } - - // Even if id='', which it will be for a duplicate title warning, we still want to return it, to avoid closing the modal - return { id }; - } catch (error: any) { - // eslint-disable-next-line no-console - console.error(error); - - toastNotifications.addDanger({ - title: i18n.translate('wizard.topNavMenu.saveVisualization.failureNotificationText', { - defaultMessage: `Error on saving '{visTitle}'`, - values: { - visTitle: newTitle, - }, - }), - text: error.message, - 'data-test-subj': 'saveVisualizationError', - }); - - // reset title if save not successful - savedWizardVis.title = currentTitle; - return { error }; - } - }; - const saveModal = ( {}} originatingApp={originatingApp} @@ -185,3 +104,101 @@ export const getTopNavConfig = ( return topNavConfig; }; + +export const getOnSave = ( + savedWizardVis, + originatingApp, + visualizationIdFromUrl, + dispatch, + services +) => { + const onSave = async ({ + newTitle, + newCopyOnSave, + isTitleDuplicateConfirmed, + onTitleDuplicate, + newDescription, + returnToOrigin, + }: OnSaveProps & { returnToOrigin: boolean }) => { + const { embeddable, toastNotifications, application, history } = services; + const stateTransfer = embeddable.getStateTransfer(); + + if (!savedWizardVis) { + return; + } + const newlyCreated = !Boolean(savedWizardVis.id) || savedWizardVis.copyOnSave; + const currentTitle = savedWizardVis.title; + savedWizardVis.title = newTitle; + savedWizardVis.description = newDescription; + savedWizardVis.copyOnSave = newCopyOnSave; + + try { + const id = await savedWizardVis.save({ + confirmOverwrite: false, + isTitleDuplicateConfirmed, + onTitleDuplicate, + returnToOrigin, + }); + + if (id) { + toastNotifications.addSuccess({ + title: i18n.translate('wizard.topNavMenu.saveVisualization.successNotificationText', { + defaultMessage: `Saved '{visTitle}'`, + values: { + visTitle: savedWizardVis.title, + }, + }), + 'data-test-subj': 'saveVisualizationSuccess', + }); + + if (originatingApp && returnToOrigin) { + // create or edit wizard directly from a dashboard + if (newlyCreated && stateTransfer) { + // create and add a new wizard to the dashboard + stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { + state: { type: 'wizard', input: { savedObjectId: id } }, + }); + return { id }; + } else { + // edit an existing wizard from the dashboard + application.navigateToApp(originatingApp); + } + } + + // Update URL + if (id !== visualizationIdFromUrl) { + history.push({ + ...history.location, + pathname: `${EDIT_PATH}/${id}`, + }); + } + dispatch(setEditorState({ state: 'clean' })); + } else { + // reset title if save not successful + savedWizardVis.title = currentTitle; + } + + // Even if id='', which it will be for a duplicate title warning, we still want to return it, to avoid closing the modal + return { id }; + } catch (error: any) { + // eslint-disable-next-line no-console + console.error(error); + + toastNotifications.addDanger({ + title: i18n.translate('wizard.topNavMenu.saveVisualization.failureNotificationText', { + defaultMessage: `Error on saving '{visTitle}'`, + values: { + visTitle: newTitle, + }, + }), + text: error.message, + 'data-test-subj': 'saveVisualizationError', + }); + + // reset title if save not successful + savedWizardVis.title = currentTitle; + return { error }; + } + }; + return onSave; +}; diff --git a/src/plugins/wizard/public/application/utils/mocks.ts b/src/plugins/wizard/public/application/utils/mocks.ts new file mode 100644 index 000000000000..574531bf6adc --- /dev/null +++ b/src/plugins/wizard/public/application/utils/mocks.ts @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ScopedHistory } from '../../../../../core/public'; +import { coreMock, scopedHistoryMock } from '../../../../../core/public/mocks'; +import { dataPluginMock } from '../../../../data/public/mocks'; +import { embeddablePluginMock } from '../../../../embeddable/public/mocks'; +import { expressionsPluginMock } from '../../../../expressions/public/mocks'; +import { navigationPluginMock } from '../../../../navigation/public/mocks'; +import { WizardServices } from '../../types'; + +export const createWizardServicesMock = () => { + const coreStartMock = coreMock.createStart(); + const toastNotifications = coreStartMock.notifications.toasts; + const applicationMock = coreStartMock.application; + const i18nContextMock = coreStartMock.i18n.Context; + const indexPatternMock = dataPluginMock.createStartContract().indexPatterns; + const embeddableMock = embeddablePluginMock.createStartContract(); + const scopedhistoryMock = (scopedHistoryMock.create() as unknown) as ScopedHistory; + const navigationMock = navigationPluginMock.createStartContract(); + const expressionMock = expressionsPluginMock.createStartContract(); + + const wizardServicesMock = { + ...coreStartMock, + navigation: navigationMock, + expression: expressionMock, + savedWizardLoader: { + get: jest.fn(), + } as any, + setHeaderActionMenu: () => {}, + applicationMock, + history: { + push: jest.fn(), + location: { pathname: '' }, + }, + toastNotifications, + i18n: i18nContextMock, + data: indexPatternMock, + embeddable: embeddableMock, + scopedHistory: scopedhistoryMock, + }; + + return (wizardServicesMock as unknown) as jest.Mocked; +}; From dfd78092d99f585f3cdc9b0e44a1543b9cb65e33 Mon Sep 17 00:00:00 2001 From: abbyhu2000 Date: Fri, 30 Sep 2022 21:35:19 +0000 Subject: [PATCH 4/4] Add changelog Signed-off-by: abbyhu2000 --- CHANGELOG.md | 1 + config/opensearch_dashboards.yml | 1 - .../wizard/public/application/components/top_nav.tsx | 10 +++++++++- .../application/utils/get_top_nav_config.test.tsx | 4 +++- .../public/application/utils/get_top_nav_config.tsx | 10 +++++----- src/plugins/wizard/public/application/utils/mocks.ts | 3 +-- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57181ef33f45..df35a1f20ba5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Add updated_at column to objects' tables ([#1218](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/1218)) * [Viz Builder] State validation before dispatching and loading ([#2351](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2351)) +* [Viz Builder] Create a new wizard directly on a dashboard ([#2384](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2384)) ### 🐛 Bug Fixes diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index da2dfb10d9c3..4f23dc42c050 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -232,4 +232,3 @@ #data_source.encryption.wrappingKeyName: 'changeme' #data_source.encryption.wrappingKeyNamespace: 'changeme' #data_source.encryption.wrappingKey: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] - diff --git a/src/plugins/wizard/public/application/components/top_nav.tsx b/src/plugins/wizard/public/application/components/top_nav.tsx index 173709264f5c..470babdf782f 100644 --- a/src/plugins/wizard/public/application/components/top_nav.tsx +++ b/src/plugins/wizard/public/application/components/top_nav.tsx @@ -53,7 +53,15 @@ export const TopNav = () => { }; setConfig(getConfig()); - }, [rootState, savedWizardVis, services, visualizationIdFromUrl, saveDisabledReason, dispatch, indexPattern]); + }, [ + rootState, + savedWizardVis, + services, + visualizationIdFromUrl, + saveDisabledReason, + dispatch, + indexPattern, + ]); // reset validity before component destroyed useUnmount(() => { diff --git a/src/plugins/wizard/public/application/utils/get_top_nav_config.test.tsx b/src/plugins/wizard/public/application/utils/get_top_nav_config.test.tsx index 456297d99a59..3928858d08e8 100644 --- a/src/plugins/wizard/public/application/utils/get_top_nav_config.test.tsx +++ b/src/plugins/wizard/public/application/utils/get_top_nav_config.test.tsx @@ -120,7 +120,7 @@ describe('getOnSave', () => { }); test('create a new wizard from dashboard', async () => { - savedWizardVis.id = null; + savedWizardVis.id = undefined; savedWizardVis.save = jest.fn().mockReturnValue('2'); originatingApp = 'dashboard'; onSaveProps.returnToOrigin = true; @@ -139,6 +139,7 @@ describe('getOnSave', () => { test('edit an exising wizard from dashboard', async () => { savedWizardVis.copyOnSave = false; + onSaveProps.newDescription = 'new description after editing'; originatingApp = 'dashboard'; onSaveProps.returnToOrigin = true; const onSave = getOnSave( @@ -151,5 +152,6 @@ describe('getOnSave', () => { const onSaveResult = await onSave(onSaveProps); expect(onSaveResult?.id).toBe('1'); expect(mockServices.application.navigateToApp).toBeCalledTimes(1); + expect(savedWizardVis.description).toBe('new description after editing'); }); }); diff --git a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx index 529d91f76f86..1afd9358d50d 100644 --- a/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/wizard/public/application/utils/get_top_nav_config.tsx @@ -58,7 +58,7 @@ export const getTopNavConfig = ( scopedHistory, } = services; - const { originatingApp: originatingApp } = + const { originatingApp } = embeddable .getStateTransfer(scopedHistory) .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'input'] }) || {}; @@ -126,7 +126,7 @@ export const getOnSave = ( if (!savedWizardVis) { return; } - const newlyCreated = !Boolean(savedWizardVis.id) || savedWizardVis.copyOnSave; + const newlyCreated = !savedWizardVis.id || savedWizardVis.copyOnSave; const currentTitle = savedWizardVis.title; savedWizardVis.title = newTitle; savedWizardVis.description = newDescription; @@ -152,15 +152,15 @@ export const getOnSave = ( }); if (originatingApp && returnToOrigin) { - // create or edit wizard directly from a dashboard + // create or edit wizard directly from another app, such as `dashboard` if (newlyCreated && stateTransfer) { - // create and add a new wizard to the dashboard + // create new embeddable to transfer to originatingApp stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { state: { type: 'wizard', input: { savedObjectId: id } }, }); return { id }; } else { - // edit an existing wizard from the dashboard + // update an existing wizard from another app application.navigateToApp(originatingApp); } } diff --git a/src/plugins/wizard/public/application/utils/mocks.ts b/src/plugins/wizard/public/application/utils/mocks.ts index 574531bf6adc..3898b3121164 100644 --- a/src/plugins/wizard/public/application/utils/mocks.ts +++ b/src/plugins/wizard/public/application/utils/mocks.ts @@ -18,7 +18,6 @@ export const createWizardServicesMock = () => { const i18nContextMock = coreStartMock.i18n.Context; const indexPatternMock = dataPluginMock.createStartContract().indexPatterns; const embeddableMock = embeddablePluginMock.createStartContract(); - const scopedhistoryMock = (scopedHistoryMock.create() as unknown) as ScopedHistory; const navigationMock = navigationPluginMock.createStartContract(); const expressionMock = expressionsPluginMock.createStartContract(); @@ -39,7 +38,7 @@ export const createWizardServicesMock = () => { i18n: i18nContextMock, data: indexPatternMock, embeddable: embeddableMock, - scopedHistory: scopedhistoryMock, + scopedHistory: (scopedHistoryMock.create() as unknown) as ScopedHistory, }; return (wizardServicesMock as unknown) as jest.Mocked;