diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx index e9fdc67572ad09..049bfaf00e5dde 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx @@ -132,188 +132,208 @@ export async function runInteractiveSave(this: DashboardContainer, interactionMo componentState: { lastSavedId, managed }, } = this.getState(); - return new Promise(async (resolve, reject) => { - try { - if (interactionMode === ViewMode.EDIT && managed) { - resolve(undefined); - } - const onSaveAttempt = async ({ - newTags, - newTitle, - newDescription, - newCopyOnSave, - newTimeRestore, - onTitleDuplicate, + return new Promise((resolve, reject) => { + if (interactionMode === ViewMode.EDIT && managed) { + resolve(undefined); + } + + const onSaveAttempt = async ({ + newTags, + newTitle, + newDescription, + newCopyOnSave, + newTimeRestore, + onTitleDuplicate, + isTitleDuplicateConfirmed, + }: DashboardSaveOptions): Promise => { + const saveOptions = { + confirmOverwrite: false, isTitleDuplicateConfirmed, - }: DashboardSaveOptions): Promise => { - const saveOptions = { - confirmOverwrite: false, - isTitleDuplicateConfirmed, - onTitleDuplicate, - saveAsCopy: lastSavedId ? true : newCopyOnSave, - }; - try { - if ( - !(await checkForDuplicateDashboardTitle({ - title: newTitle, - onTitleDuplicate, - lastSavedTitle: currentState.title, - copyOnSave: saveOptions.saveAsCopy, - isTitleDuplicateConfirmed, - })) - ) { - return {}; - } - const stateFromSaveModal: DashboardStateFromSaveModal = { - title: newTitle, - tags: [] as string[], - description: newDescription, - timeRestore: newTimeRestore, - timeRange: newTimeRestore ? timefilter.getTime() : undefined, - refreshInterval: newTimeRestore ? timefilter.getRefreshInterval() : undefined, - }; - if (hasSavedObjectsTagging && newTags) { - // remove `hasSavedObjectsTagging` once the savedObjectsTagging service is optional - stateFromSaveModal.tags = newTags; - } - let dashboardStateToSave: DashboardContainerInput & { - controlGroupInput?: PersistableControlGroupInput; - } = { - ...currentState, - ...stateFromSaveModal, - }; - let persistableControlGroupInput: PersistableControlGroupInput | undefined; - if (this.controlGroup) { - persistableControlGroupInput = this.controlGroup.getPersistableInput(); - dashboardStateToSave = { - ...dashboardStateToSave, - controlGroupInput: persistableControlGroupInput, - }; - } - const { panels: nextPanels, references } = await serializeAllPanelState(this); - const newPanels = await (async () => { - if (!managed) return nextPanels; - - // this is a managed dashboard - unlink all by reference embeddables on clone - const unlinkedPanels: DashboardPanelMap = {}; - for (const [panelId, panel] of Object.entries(nextPanels)) { - const child = this.getChild(panelId); - if ( - child && - isReferenceOrValueEmbeddable(child) && - child.inputIsRefType(child.getInput() as EmbeddableInput) - ) { - const valueTypeInput = await child.getInputAsValueType(); - unlinkedPanels[panelId] = { - ...panel, - explicitInput: valueTypeInput, - }; - continue; - } - unlinkedPanels[panelId] = panel; - } - return unlinkedPanels; - })(); - const beforeAddTime = window.performance.now(); - const saveResult = await saveDashboardState({ - panelReferences: references, - saveOptions, - currentState: { - ...dashboardStateToSave, - panels: newPanels, - title: newTitle, - }, - lastSavedId, - }); - const addDuration = window.performance.now() - beforeAddTime; - reportPerformanceMetricEvent(pluginServices.getServices().analytics, { - eventName: SAVED_OBJECT_POST_TIME, - duration: addDuration, - meta: { - saved_object_type: DASHBOARD_CONTENT_ID, - }, - }); - stateFromSaveModal.lastSavedId = saveResult.id; - if (saveResult.id) { - batch(() => { - this.dispatch.setStateFromSaveModal(stateFromSaveModal); - this.dispatch.setLastSavedInput(dashboardStateToSave); - if (this.controlGroup && persistableControlGroupInput) { - this.controlGroup.setSavedState(persistableControlGroupInput); - } - }); - } - this.savedObjectReferences = saveResult.references ?? []; - this.saveNotification$.next(); - resolve(saveResult); - return saveResult; - } catch (error) { - reject(error); - return error; - } + onTitleDuplicate, + saveAsCopy: lastSavedId ? true : newCopyOnSave, }; - let customModalTitle; - let newTitle = currentState.title; - if (lastSavedId) { - const [baseTitle, baseCount] = extractTitleAndCount(newTitle); - let copyCount = baseCount + 1; - newTitle = `${baseTitle} (${copyCount})`; - - // increment count until we find a unique title - while ( + try { + if ( !(await checkForDuplicateDashboardTitle({ title: newTitle, + onTitleDuplicate, lastSavedTitle: currentState.title, - copyOnSave: true, - isTitleDuplicateConfirmed: false, + copyOnSave: saveOptions.saveAsCopy, + isTitleDuplicateConfirmed, })) ) { - copyCount++; - newTitle = `${baseTitle} (${copyCount})`; + return {}; + } + + const stateFromSaveModal: DashboardStateFromSaveModal = { + title: newTitle, + tags: [] as string[], + description: newDescription, + timeRestore: newTimeRestore, + timeRange: newTimeRestore ? timefilter.getTime() : undefined, + refreshInterval: newTimeRestore ? timefilter.getRefreshInterval() : undefined, + }; + + if (hasSavedObjectsTagging && newTags) { + // remove `hasSavedObjectsTagging` once the savedObjectsTagging service is optional + stateFromSaveModal.tags = newTags; + } + + let dashboardStateToSave: DashboardContainerInput & { + controlGroupInput?: PersistableControlGroupInput; + } = { + ...currentState, + ...stateFromSaveModal, + }; + + let persistableControlGroupInput: PersistableControlGroupInput | undefined; + if (this.controlGroup) { + persistableControlGroupInput = this.controlGroup.getPersistableInput(); + dashboardStateToSave = { + ...dashboardStateToSave, + controlGroupInput: persistableControlGroupInput, + }; } - switch (interactionMode) { - case ViewMode.EDIT: { - customModalTitle = i18n.translate( - 'dashboard.topNav.editModeInteractiveSave.modalTitle', - { - defaultMessage: 'Save as new dashboard', - } - ); - break; + const { panels: nextPanels, references } = await serializeAllPanelState(this); + + const newPanels = await (async () => { + if (!managed) return nextPanels; + + // this is a managed dashboard - unlink all by reference embeddables on clone + const unlinkedPanels: DashboardPanelMap = {}; + for (const [panelId, panel] of Object.entries(nextPanels)) { + const child = this.getChild(panelId); + if ( + child && + isReferenceOrValueEmbeddable(child) && + child.inputIsRefType(child.getInput() as EmbeddableInput) + ) { + const valueTypeInput = await child.getInputAsValueType(); + unlinkedPanels[panelId] = { + ...panel, + explicitInput: valueTypeInput, + }; + continue; + } + unlinkedPanels[panelId] = panel; } - case ViewMode.VIEW: { - customModalTitle = i18n.translate( - 'dashboard.topNav.viewModeInteractiveSave.modalTitle', - { - defaultMessage: 'Duplicate dashboard', - } - ); - break; + return unlinkedPanels; + })(); + + const beforeAddTime = window.performance.now(); + + const saveResult = await saveDashboardState({ + panelReferences: references, + saveOptions, + currentState: { + ...dashboardStateToSave, + panels: newPanels, + title: newTitle, + }, + lastSavedId, + }); + + const addDuration = window.performance.now() - beforeAddTime; + + reportPerformanceMetricEvent(pluginServices.getServices().analytics, { + eventName: SAVED_OBJECT_POST_TIME, + duration: addDuration, + meta: { + saved_object_type: DASHBOARD_CONTENT_ID, + }, + }); + + stateFromSaveModal.lastSavedId = saveResult.id; + + if (saveResult.id) { + batch(() => { + this.dispatch.setStateFromSaveModal(stateFromSaveModal); + this.dispatch.setLastSavedInput(dashboardStateToSave); + if (this.controlGroup && persistableControlGroupInput) { + this.controlGroup.setSavedState(persistableControlGroupInput); + } + }); + } + + this.savedObjectReferences = saveResult.references ?? []; + this.saveNotification$.next(); + + resolve(saveResult); + return saveResult; + } catch (error) { + reject(error); + return error; + } + }; + + (async () => { + try { + let customModalTitle; + let newTitle = currentState.title; + + if (lastSavedId) { + const [baseTitle, baseCount] = extractTitleAndCount(newTitle); + let copyCount = baseCount + 1; + newTitle = `${baseTitle} (${copyCount})`; + + // increment count until we find a unique title + while ( + !(await checkForDuplicateDashboardTitle({ + title: newTitle, + lastSavedTitle: currentState.title, + copyOnSave: true, + isTitleDuplicateConfirmed: false, + })) + ) { + copyCount++; + newTitle = `${baseTitle} (${copyCount})`; } - default: { - customModalTitle = undefined; + + switch (interactionMode) { + case ViewMode.EDIT: { + customModalTitle = i18n.translate( + 'dashboard.topNav.editModeInteractiveSave.modalTitle', + { + defaultMessage: 'Save as new dashboard', + } + ); + break; + } + case ViewMode.VIEW: { + customModalTitle = i18n.translate( + 'dashboard.topNav.viewModeInteractiveSave.modalTitle', + { + defaultMessage: 'Duplicate dashboard', + } + ); + break; + } + default: { + customModalTitle = undefined; + } } } + + const dashboardDuplicateModal = ( + resolve(undefined)} + timeRestore={currentState.timeRestore} + showStoreTimeOnSave={!lastSavedId} + description={currentState.description ?? ''} + showCopyOnSave={false} + onSave={onSaveAttempt} + customModalTitle={customModalTitle} + /> + ); + this.clearOverlays(); + showSaveModal(dashboardDuplicateModal); + } catch (error) { + reject(error); } - const dashboardDuplicateModal = ( - resolve(undefined)} - timeRestore={currentState.timeRestore} - showStoreTimeOnSave={!lastSavedId} - description={currentState.description ?? ''} - showCopyOnSave={false} - onSave={onSaveAttempt} - customModalTitle={customModalTitle} - /> - ); - this.clearOverlays(); - showSaveModal(dashboardDuplicateModal); - } catch (error) { - reject(error); - } + })(); }); } diff --git a/test/functional/apps/dashboard/group4/dashboard_clone.ts b/test/functional/apps/dashboard/group4/dashboard_clone.ts index b027d52a852ab2..1e15f4d44e7405 100644 --- a/test/functional/apps/dashboard/group4/dashboard_clone.ts +++ b/test/functional/apps/dashboard/group4/dashboard_clone.ts @@ -35,13 +35,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await listingTable.searchAndExpectItemsCount('dashboard', clonedDashboardName, 1); }); - it('Clone should suggest a unique title', async function () { - await PageObjects.dashboard.loadSavedDashboard(clonedDashboardName); - await PageObjects.dashboard.duplicateDashboard(); - await PageObjects.dashboard.gotoDashboardLandingPage; - await listingTable.searchAndExpectItemsCount('dashboard', `${clonedDashboardName} (2)`, 1); - }); - it('the copy should have all the same visualizations', async function () { await PageObjects.dashboard.loadSavedDashboard(clonedDashboardName); await retry.try(async () => { @@ -49,5 +42,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(panelTitles).to.eql(PageObjects.dashboard.getTestVisualizationNames()); }); }); + + it('Clone should suggest a unique title', async function () { + await PageObjects.dashboard.loadSavedDashboard(clonedDashboardName); + await PageObjects.dashboard.duplicateDashboard(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await listingTable.searchAndExpectItemsCount('dashboard', `${dashboardName} (2)`, 1); + }); }); }