diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index e47968b027cc3..42adf6f2a950b 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -23,5 +23,5 @@ "ui": true, "server": true, "extraPublicDirs": ["common/constants"], - "requiredBundles": ["kibanaReact", "kibanaUtils", "home", "mapsOss"] + "requiredBundles": ["kibanaReact", "kibanaUtils", "home", "mapsOss", "presentationUtil"] } diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts index 27fd78980710f..df424124be3f2 100644 --- a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts @@ -281,10 +281,12 @@ export class SavedMap { returnToOrigin, newTags, saveByReference, + dashboardId, }: OnSaveProps & { - returnToOrigin: boolean; + returnToOrigin?: boolean; newTags?: string[]; saveByReference: boolean; + dashboardId?: string | null; }) { if (!this._attributes) { throw new Error('Invalid usage, must await whenReady before calling save'); @@ -337,7 +339,7 @@ export class SavedMap { }); return; } - this._getStateTransfer().navigateToWithEmbeddablePackage(this._originatingApp, { + await this._getStateTransfer().navigateToWithEmbeddablePackage(this._originatingApp, { state: { embeddableId: newCopyOnSave ? undefined : this._embeddableId, type: MAP_SAVED_OBJECT_TYPE, @@ -345,6 +347,15 @@ export class SavedMap { }, }); return; + } else if (dashboardId) { + await this._getStateTransfer().navigateToWithEmbeddablePackage('dashboards', { + state: { + type: MAP_SAVED_OBJECT_TYPE, + input: updatedMapEmbeddableInput, + }, + path: dashboardId === 'new' ? '#/create' : `#/view/${dashboardId}`, + }); + return; } this._mapEmbeddableInput = updatedMapEmbeddableInput; diff --git a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx index 43a74a9c73012..7010c281d24c6 100644 --- a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx @@ -10,6 +10,7 @@ import { Adapters } from 'src/plugins/inspector/public'; import { getCoreChrome, getMapsCapabilities, + getIsAllowByValueEmbeddables, getInspector, getCoreI18n, getSavedObjectsClient, @@ -25,6 +26,7 @@ import { import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; import { SavedMap } from './saved_map'; import { getMapEmbeddableDisplayName } from '../../../common/i18n_getters'; +import { SavedObjectSaveModalDashboard } from '../../../../../../src/plugins/presentation_util/public'; export function getTopNavConfig({ savedMap, @@ -139,53 +141,67 @@ export function getTopNavConfig({ /> ) : undefined; - const saveModal = ( - { - try { - await checkForDuplicateTitle( - { - id: props.newCopyOnSave ? undefined : savedMap.getSavedObjectId(), - title: props.newTitle, - copyOnSave: props.newCopyOnSave, - lastSavedTitle: savedMap.getSavedObjectId() ? savedMap.getTitle() : '', - getEsType: () => MAP_SAVED_OBJECT_TYPE, - getDisplayName: getMapEmbeddableDisplayName, - }, - props.isTitleDuplicateConfirmed, - props.onTitleDuplicate, - { - savedObjectsClient: getSavedObjectsClient(), - overlays: getCoreOverlays(), - } - ); - } catch (e) { - // ignore duplicate title failure, user notified in save modal - return {}; - } + const saveModalProps = { + onSave: async ( + props: OnSaveProps & { returnToOrigin?: boolean; dashboardId?: string | null } + ) => { + try { + await checkForDuplicateTitle( + { + id: props.newCopyOnSave ? undefined : savedMap.getSavedObjectId(), + title: props.newTitle, + copyOnSave: props.newCopyOnSave, + lastSavedTitle: savedMap.getSavedObjectId() ? savedMap.getTitle() : '', + getEsType: () => MAP_SAVED_OBJECT_TYPE, + getDisplayName: getMapEmbeddableDisplayName, + }, + props.isTitleDuplicateConfirmed, + props.onTitleDuplicate, + { + savedObjectsClient: getSavedObjectsClient(), + overlays: getCoreOverlays(), + } + ); + } catch (e) { + // ignore duplicate title failure, user notified in save modal + return {}; + } + + await savedMap.save({ + ...props, + newTags: selectedTags, + saveByReference: !props.dashboardId, + }); + // showSaveModal wrapper requires onSave to return an object with an id to close the modal after successful save + return { id: 'id' }; + }, + onClose: () => {}, + documentInfo: { + description: mapDescription, + id: savedMap.getSavedObjectId(), + title: savedMap.getTitle(), + }, + objectType: i18n.translate('xpack.maps.topNav.saveModalType', { + defaultMessage: 'map', + }), + }; + + const saveModal = + savedMap.getOriginatingApp() || !getIsAllowByValueEmbeddables() ? ( + + ) : ( + + ); - await savedMap.save({ - ...props, - newTags: selectedTags, - saveByReference: true, - }); - // showSaveModal wrapper requires onSave to return an object with an id to close the modal after successful save - return { id: 'id' }; - }} - onClose={() => {}} - documentInfo={{ - description: mapDescription, - id: savedMap.getSavedObjectId(), - title: savedMap.getTitle(), - }} - objectType={i18n.translate('xpack.maps.topNav.saveModalType', { - defaultMessage: 'map', - })} - options={tagSelector} - /> - ); showSaveModal(saveModal, getCoreI18n().Context); }, }); diff --git a/x-pack/test/functional/apps/maps/embeddable/save_and_return.js b/x-pack/test/functional/apps/maps/embeddable/save_and_return.js index 40af8ddb9d44b..13aa08cbbeb38 100644 --- a/x-pack/test/functional/apps/maps/embeddable/save_and_return.js +++ b/x-pack/test/functional/apps/maps/embeddable/save_and_return.js @@ -31,6 +31,7 @@ export default function ({ getPageObjects, getService }) { after(async () => { await security.testUser.restoreDefaults(); }); + describe('new map', () => { beforeEach(async () => { await PageObjects.common.navigateToApp('dashboard'); @@ -55,7 +56,7 @@ export default function ({ getPageObjects, getService }) { it('should cut the originator and stay in maps application', async () => { await PageObjects.maps.saveMap( 'map created from dashboard save and return with originator app cut', - true + false ); await PageObjects.maps.waitForLayersToLoad(); await testSubjects.missingOrFail('mapSaveAndReturnButton'); @@ -94,7 +95,7 @@ export default function ({ getPageObjects, getService }) { describe('save as and uncheck return to origin switch', () => { it('should cut the originator and stay in maps application', async () => { - await PageObjects.maps.saveMap('Clone 2 of map embeddable example', true); + await PageObjects.maps.saveMap('Clone 2 of map embeddable example', false); await PageObjects.maps.waitForLayersToLoad(); await testSubjects.missingOrFail('mapSaveAndReturnButton'); await testSubjects.existOrFail('mapSaveButton'); diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index 7e22acf785d36..7767499cb6bd3 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -9,7 +9,7 @@ import { APP_ID } from '../../../plugins/maps/common/constants'; import { FtrProviderContext } from '../ftr_provider_context'; export function GisPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'header', 'timePicker']); + const PageObjects = getPageObjects(['common', 'header', 'timePicker', 'visualize']); const log = getService('log'); const testSubjects = getService('testSubjects'); @@ -148,18 +148,14 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte await renderable.waitForRender(); } - async saveMap(name: string, uncheckReturnToOriginModeSwitch = false, tags?: string[]) { + async saveMap(name: string, redirectToOrigin = true, tags?: string[]) { await testSubjects.click('mapSaveButton'); await testSubjects.setValue('savedObjectTitle', name); - if (uncheckReturnToOriginModeSwitch) { - const redirectToOriginCheckboxExists = await testSubjects.exists( - 'returnToOriginModeSwitch' - ); - if (!redirectToOriginCheckboxExists) { - throw new Error('Unable to uncheck "returnToOriginModeSwitch", it does not exist.'); - } - await testSubjects.setEuiSwitch('returnToOriginModeSwitch', 'uncheck'); - } + await PageObjects.visualize.setSaveModalValues(name, { + addToDashboard: false, + redirectToOrigin, + saveAsNew: true, + }); if (tags) { await testSubjects.click('savedObjectTagSelector'); for (const tagName of tags) { diff --git a/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts index 4e44659b4fc67..32b9cc378db45 100644 --- a/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts +++ b/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts @@ -13,7 +13,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const listingTable = getService('listingTable'); const testSubjects = getService('testSubjects'); const find = getService('find'); - const PageObjects = getPageObjects(['maps', 'tagManagement', 'common']); + const PageObjects = getPageObjects(['maps', 'tagManagement', 'common', 'visualize']); /** * Select tags in the searchbar's tag filter. @@ -78,7 +78,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('allows to select tags for a new map', async () => { - await PageObjects.maps.saveMap('my-new-map', false, ['tag-1', 'tag-3']); + await PageObjects.maps.saveMap('my-new-map', true, ['tag-1', 'tag-3']); await PageObjects.maps.gotoMapListingPage(); await selectFilterTags('tag-1'); @@ -91,6 +91,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.click('mapSaveButton'); await testSubjects.setValue('savedObjectTitle', 'map-with-new-tag'); + await PageObjects.visualize.setSaveModalValues('map-with-new-tag', { + addToDashboard: false, + saveAsNew: true, + }); await testSubjects.click('savedObjectTagSelector'); await testSubjects.click(`tagSelectorOption-action__create`); @@ -127,7 +131,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('allows to select tags for an existing map', async () => { await listingTable.clickItemLink('map', 'map 4 (tag-1)'); - await PageObjects.maps.saveMap('map 4 (tag-1)', false, ['tag-3']); + await PageObjects.maps.saveMap('map 4 (tag-1)', true, ['tag-3']); await PageObjects.maps.gotoMapListingPage(); await selectFilterTags('tag-3');