diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx index 77afe92a8f521..b757635af1702 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx @@ -77,6 +77,7 @@ export function EmbeddedMapComponent() { ); const input: MapEmbeddableInput = { + attributes: { title: '' }, id: uuid.v4(), filters: mapFilters, refreshConfig: { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts index a64ff7da2aa19..7bb6e43b38d59 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts @@ -83,6 +83,7 @@ export function savedMap(): ExpressionFunctionDefinition< return { type: EmbeddableExpressionType, input: { + attributes: { title: '' }, id: args.id, filters: getQueryFilters(filters), timeRange: args.timerange || defaultTimeRange, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts index d2c803a1ff208..635e0ec2d0dcb 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts @@ -9,6 +9,7 @@ import { MapEmbeddableInput } from '../../../../../../plugins/maps/public/embedd import { fromExpression, Ast } from '@kbn/interpreter/common'; const baseSavedMapInput = { + attributes: { title: '' }, id: 'embeddableId', filters: [], isLayerTOCOpen: false, diff --git a/x-pack/plugins/maps/common/i18n_getters.ts b/x-pack/plugins/maps/common/i18n_getters.ts index 0008a119f1c7c..a128038e321fc 100644 --- a/x-pack/plugins/maps/common/i18n_getters.ts +++ b/x-pack/plugins/maps/common/i18n_getters.ts @@ -15,6 +15,12 @@ export function getAppTitle() { }); } +export function getMapEmbeddableDisplayName() { + return i18n.translate('xpack.maps.embeddableDisplayName', { + defaultMessage: 'map', + }); +} + export function getDataSourceLabel() { return i18n.translate('xpack.maps.source.dataSourceLabel', { defaultMessage: 'Data source', diff --git a/x-pack/plugins/maps/common/map_saved_object_type.ts b/x-pack/plugins/maps/common/map_saved_object_type.ts index e195d9e4538f0..ce9ffdc3b221d 100644 --- a/x-pack/plugins/maps/common/map_saved_object_type.ts +++ b/x-pack/plugins/maps/common/map_saved_object_type.ts @@ -8,7 +8,7 @@ import { SavedObject } from '../../../../src/core/types/saved_objects'; export type MapSavedObjectAttributes = { - title?: string; + title: string; description?: string; mapStateJSON?: string; layerListJSON?: string; diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index f52feb3552abb..8983d4ab1a4da 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -12,6 +12,7 @@ "uiActions", "navigation", "visualizations", + "dashboard", "embeddable", "mapsLegacy", "usageCollection", diff --git a/x-pack/plugins/maps/public/actions/layer_actions.test.ts b/x-pack/plugins/maps/public/actions/layer_actions.test.ts index 09a22dca271d7..495243d62ca36 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.test.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.test.ts @@ -8,6 +8,14 @@ import { addLayer } from './layer_actions'; import { LayerDescriptor } from '../../common/descriptor_types'; import { LICENSED_FEATURES } from '../licensed_features'; +jest.mock('../kibana_services', () => { + return { + getMapsCapabilities() { + return { save: true }; + }, + }; +}); + const getStoreMock = jest.fn(); const dispatchMock = jest.fn(); diff --git a/x-pack/plugins/maps/public/actions/map_actions.test.js b/x-pack/plugins/maps/public/actions/map_actions.test.js index cbb6f0a4054cc..1d1f8a511c4fa 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.test.js +++ b/x-pack/plugins/maps/public/actions/map_actions.test.js @@ -10,6 +10,13 @@ jest.mock('./data_request_actions', () => { syncDataForAllLayers: () => {}, }; }); +jest.mock('../kibana_services', () => { + return { + getMapsCapabilities() { + return { save: true }; + }, + }; +}); import { mapExtentChanged, setMouseCoordinates, setQuery } from './map_actions'; diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.test.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.test.tsx index c03aa1e098540..6381285a8ef70 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.test.tsx @@ -7,6 +7,14 @@ import React from 'react'; import { shallow } from 'enzyme'; +jest.mock('../../kibana_services', () => { + return { + getMapsCapabilities() { + return { save: true }; + }, + }; +}); + // @ts-ignore import { ToolbarOverlay } from './toolbar_overlay'; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js index 543be9395d0bc..877e8658ab03e 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js @@ -7,6 +7,14 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +jest.mock('../../../../../kibana_services', () => { + return { + getMapsCapabilities() { + return { save: true }; + }, + }; +}); + import { TOCEntry } from './view'; const LAYER_ID = '1'; diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index f20656545d09a..caf21431145d5 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -10,7 +10,11 @@ import { Provider } from 'react-redux'; import { render, unmountComponentAtNode } from 'react-dom'; import { Subscription } from 'rxjs'; import { Unsubscribe } from 'redux'; -import { Embeddable, IContainer } from '../../../../../src/plugins/embeddable/public'; +import { + Embeddable, + IContainer, + ReferenceOrValueEmbeddable, +} from '../../../../../src/plugins/embeddable/public'; import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../src/plugins/data/public'; import { APPLY_FILTER_TRIGGER, @@ -24,10 +28,7 @@ import { Query, RefreshInterval, } from '../../../../../src/plugins/data/public'; -import { createMapStore, MapStore } from '../reducers/store'; -import { MapSettings } from '../reducers/map'; import { - setGotoWithCenter, replaceLayerList, setQuery, setRefreshConfig, @@ -37,11 +38,7 @@ import { hideToolbarOverlay, hideLayerControl, hideViewControl, - setHiddenLayers, - setMapSettings, setReadOnly, - setIsLayerTOCOpen, - setOpenTOCDetails, } from '../actions'; import { getIsLayerTOCOpen, getOpenTOCDetails } from '../selectors/ui_selectors'; import { @@ -49,24 +46,42 @@ import { setEventHandlers, EventHandlers, } from '../reducers/non_serializable_instances'; -import { getMapCenter, getMapZoom, getHiddenLayerIds } from '../selectors/map_selectors'; -import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; +import { + getMapCenter, + getMapZoom, + getHiddenLayerIds, + getQueryableUniqueIndexPatternIds, +} from '../selectors/map_selectors'; +import { + APP_ID, + getExistingMapPath, + MAP_SAVED_OBJECT_TYPE, + MAP_PATH, +} from '../../common/constants'; import { RenderToolTipContent } from '../classes/tooltips/tooltip_property'; -import { getUiActions, getCoreI18n } from '../kibana_services'; +import { getUiActions, getCoreI18n, getHttp } from '../kibana_services'; import { LayerDescriptor } from '../../common/descriptor_types'; import { MapContainer } from '../connected_components/map_container'; +import { SavedMap } from '../routes/map_page'; +import { getIndexPatternsFromIds } from '../index_pattern_util'; +import { getMapAttributeService } from '../map_attribute_service'; -import { MapEmbeddableConfig, MapEmbeddableInput, MapEmbeddableOutput } from './types'; -export { MapEmbeddableInput, MapEmbeddableConfig }; - -export class MapEmbeddable extends Embeddable { +import { + MapByValueInput, + MapByReferenceInput, + MapEmbeddableConfig, + MapEmbeddableInput, + MapEmbeddableOutput, +} from './types'; +export { MapEmbeddableInput }; + +export class MapEmbeddable + extends Embeddable + implements ReferenceOrValueEmbeddable { type = MAP_SAVED_OBJECT_TYPE; - private _description: string; + private _savedMap: SavedMap; private _renderTooltipContent?: RenderToolTipContent; - private _eventHandlers?: EventHandlers; - private _layerList: LayerDescriptor[]; - private _store: MapStore; private _subscription: Subscription; private _prevTimeRange?: TimeRange; private _prevQuery?: Query; @@ -74,43 +89,119 @@ export class MapEmbeddable extends Embeddable this.onContainerStateChanged(input)); } + private async _initializeSaveMap() { + try { + await this._savedMap.whenReady(); + } catch (e) { + this.onFatalError(e); + return; + } + this._initializeStore(); + this._initializeOutput(); + this._isInitialized = true; + if (this._domNode) { + this.render(this._domNode); + } + } + + private async _initializeStore() { + const store = this._savedMap.getStore(); + store.dispatch(setReadOnly(true)); + store.dispatch(disableScrollZoom()); + + if (_.has(this.input, 'disableInteractive') && this.input.disableInteractive) { + store.dispatch(disableInteractive()); + } + + if (_.has(this.input, 'disableTooltipControl') && this.input.disableTooltipControl) { + store.dispatch(disableTooltipControl()); + } + if (_.has(this.input, 'hideToolbarOverlay') && this.input.hideToolbarOverlay) { + store.dispatch(hideToolbarOverlay()); + } + + if (_.has(this.input, 'hideLayerControl') && this.input.hideLayerControl) { + store.dispatch(hideLayerControl()); + } + + if (_.has(this.input, 'hideViewControl') && this.input.hideViewControl) { + store.dispatch(hideViewControl()); + } + + this._dispatchSetQuery({ + query: this.input.query, + timeRange: this.input.timeRange, + filters: this.input.filters, + forceRefresh: false, + }); + if (this.input.refreshConfig) { + this._dispatchSetRefreshConfig(this.input.refreshConfig); + } + + this._unsubscribeFromStore = this._savedMap.getStore().subscribe(() => { + this._handleStoreChanges(); + }); + } + + private async _initializeOutput() { + const savedMapTitle = this._savedMap.getAttributes()?.title + ? this._savedMap.getAttributes().title + : ''; + const input = this.getInput(); + const title = input.hidePanelTitles ? '' : input.title || savedMapTitle; + const savedObjectId = (input as MapByReferenceInput).savedObjectId; + this.updateOutput({ + ...this.getOutput(), + defaultTitle: savedMapTitle, + title, + editPath: `/${MAP_PATH}/${savedObjectId}`, + editUrl: getHttp().basePath.prepend(getExistingMapPath(savedObjectId)), + indexPatterns: await this._getIndexPatterns(), + }); + } + + public inputIsRefType( + input: MapByValueInput | MapByReferenceInput + ): input is MapByReferenceInput { + return getMapAttributeService().inputIsRefType(input); + } + + public async getInputAsRefType(): Promise { + const input = getMapAttributeService().getExplicitInputFromEmbeddable(this); + return getMapAttributeService().getInputAsRefType(input, { + showSaveModal: true, + saveModalTitle: this.getTitle(), + }); + } + + public async getInputAsValueType(): Promise { + const input = getMapAttributeService().getExplicitInputFromEmbeddable(this); + return getMapAttributeService().getInputAsValueType(input); + } + public getDescription() { - return this._description; + return this._isInitialized ? this._savedMap.getAttributes().description : ''; } - supportedTriggers(): Array { + public supportedTriggers(): Array { return [APPLY_FILTER_TRIGGER]; } @@ -119,11 +210,11 @@ export class MapEmbeddable extends Embeddable { - this._eventHandlers = eventHandlers; + this._savedMap.getStore().dispatch(setEventHandlers(eventHandlers)); }; getInspectorAdapters() { - return getInspectorAdapters(this._store.getState()); + return getInspectorAdapters(this._savedMap.getStore().getState()); } onContainerStateChanged(containerState: MapEmbeddableInput) { @@ -132,29 +223,37 @@ export class MapEmbeddable extends Embeddable( + this._savedMap.getStore().dispatch( setQuery({ filters: filters.filter((filter) => !filter.meta.disabled), query, @@ -164,9 +263,9 @@ export class MapEmbeddable extends Embeddable) { + _dispatchSetRefreshConfig(refreshConfig: RefreshInterval) { this._prevRefreshConfig = refreshConfig; - this._store.dispatch( + this._savedMap.getStore().dispatch( setRefreshConfig({ isPaused: refreshConfig.pause, interval: refreshConfig.value, @@ -180,64 +279,15 @@ export class MapEmbeddable extends Embeddable(replaceLayerList(this._layerList)); - if (this.input.hiddenLayers) { - this._store.dispatch(setHiddenLayers(this.input.hiddenLayers)); - } - this._dispatchSetQuery(this.input); - this._dispatchSetRefreshConfig(this.input); - this._domNode = domNode; + if (!this._isInitialized) { + return; + } const I18nContext = getCoreI18n().Context; render( - + , this._domNode ); + } - this._unsubscribeFromStore = this._store.subscribe(() => { - this._handleStoreChanges(); + setLayerList(layerList: LayerDescriptor[]) { + this._savedMap.getStore().dispatch(replaceLayerList(layerList)); + this._getIndexPatterns().then((indexPatterns) => { + this.updateOutput({ + ...this.getOutput(), + indexPatterns, + }); }); } - async setLayerList(layerList: LayerDescriptor[]) { - this._layerList = layerList; - return await this._store.dispatch(replaceLayerList(this._layerList)); + private async _getIndexPatterns() { + const queryableIndexPatternIds = getQueryableUniqueIndexPatternIds( + this._savedMap.getStore().getState() + ); + return await getIndexPatternsFromIds(queryableIndexPatternIds); } addFilters = async (filters: Filter[], actionId: string = ACTION_GLOBAL_APPLY_FILTER) => { @@ -317,8 +375,8 @@ export class MapEmbeddable extends Embeddable { - // Need to extract layerList from store to get queryable index pattern ids - const { - addLayerWithoutDataSync, - createMapStore, - getIndexPatternsFromIds, - getQueryableUniqueIndexPatternIds, - } = await lazyLoadMapModules(); - const store = createMapStore(); - let queryableIndexPatternIds: string[]; - try { - layerList.forEach((layerDescriptor: LayerDescriptor) => { - store.dispatch(addLayerWithoutDataSync(layerDescriptor)); - }); - queryableIndexPatternIds = getQueryableUniqueIndexPatternIds(store.getState()); - } catch (error) { - throw new Error( - i18n.translate('xpack.maps.mapEmbeddableFactory.invalidLayerList', { - defaultMessage: 'Unable to load map, malformed layer list', - }) - ); - } - - const indexPatterns = await getIndexPatternsFromIds(queryableIndexPatternIds); - return _.compact(indexPatterns) as IIndexPattern[]; - } - - async _fetchSavedMap(savedObjectId: string) { - const { getMapsSavedObjectLoader } = await lazyLoadMapModules(); - const savedObjectLoader = getMapsSavedObjectLoader(); - return await savedObjectLoader.get(savedObjectId); + return getMapEmbeddableDisplayName(); } createFromSavedObject = async ( @@ -87,64 +44,17 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { input: MapEmbeddableInput, parent?: IContainer ) => { - const { - getInitialLayers, - getHttp, - MapEmbeddable, - mergeInputWithSavedMap, - } = await lazyLoadMapModules(); - const savedMap = await this._fetchSavedMap(savedObjectId); - const layerList = getInitialLayers(savedMap.layerListJSON); - const indexPatterns = await this._getIndexPatterns(layerList); - - let settings; - if (savedMap.mapStateJSON) { - const mapState = JSON.parse(savedMap.mapStateJSON); - if (mapState.settings) { - settings = mapState.settings; - } - } - - const embeddable = new MapEmbeddable( - { - layerList, - title: savedMap.title, - description: savedMap.description, - editUrl: getHttp().basePath.prepend(getExistingMapPath(savedObjectId)), - editApp: APP_ID, - editPath: `/${MAP_PATH}/${savedObjectId}`, - indexPatterns, - editable: await this.isEditable(), - settings, - }, - input, - parent - ); - - try { - embeddable.updateInput(mergeInputWithSavedMap(input, savedMap)); - } catch (error) { - throw new Error( - i18n.translate('xpack.maps.mapEmbeddableFactory.invalidSavedObject', { - defaultMessage: 'Unable to load map, malformed saved object', - }) - ); + if (!(input as MapByReferenceInput).savedObjectId) { + (input as MapByReferenceInput).savedObjectId = savedObjectId; } - - return embeddable; + return this.create(input, parent); }; create = async (input: MapEmbeddableInput, parent?: IContainer) => { - const { getInitialLayers, MapEmbeddable } = await lazyLoadMapModules(); - const layerList = getInitialLayers(); - const indexPatterns = await this._getIndexPatterns(layerList); - + const { MapEmbeddable } = await lazyLoadMapModules(); return new MapEmbeddable( { - layerList, - title: input.title ?? '', - indexPatterns, - editable: false, + editable: await this.isEditable(), }, input, parent diff --git a/x-pack/plugins/maps/public/embeddable/merge_input_with_saved_map.js b/x-pack/plugins/maps/public/embeddable/merge_input_with_saved_map.js deleted file mode 100644 index d91c91b3b223c..0000000000000 --- a/x-pack/plugins/maps/public/embeddable/merge_input_with_saved_map.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 _ from 'lodash'; - -import { DEFAULT_IS_LAYER_TOC_OPEN } from '../reducers/ui'; - -const MAP_EMBEDDABLE_INPUT_KEYS = [ - 'hideFilterActions', - 'isLayerTOCOpen', - 'openTOCDetails', - 'mapCenter', -]; - -export function mergeInputWithSavedMap(input, savedMap) { - const mergedInput = _.pick(input, MAP_EMBEDDABLE_INPUT_KEYS); - - if (!_.has(input, 'isLayerTOCOpen') && savedMap.uiStateJSON) { - const uiState = JSON.parse(savedMap.uiStateJSON); - mergedInput.isLayerTOCOpen = _.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN); - } - - if (!_.has(input, 'openTOCDetails') && savedMap.uiStateJSON) { - const uiState = JSON.parse(savedMap.uiStateJSON); - if (_.has(uiState, 'openTOCDetails')) { - mergedInput.openTOCDetails = _.get(uiState, 'openTOCDetails', []); - } - } - - if (!input.mapCenter && savedMap.mapStateJSON) { - const mapState = JSON.parse(savedMap.mapStateJSON); - mergedInput.mapCenter = { - lat: mapState.center.lat, - lon: mapState.center.lon, - zoom: mapState.zoom, - }; - } - - return mergedInput; -} diff --git a/x-pack/plugins/maps/public/embeddable/types.ts b/x-pack/plugins/maps/public/embeddable/types.ts index 8ba906111ad1e..a401cafcff8ea 100644 --- a/x-pack/plugins/maps/public/embeddable/types.ts +++ b/x-pack/plugins/maps/public/embeddable/types.ts @@ -5,29 +5,22 @@ */ import { IIndexPattern } from '../../../../../src/plugins/data/common/index_patterns'; -import { MapSettings } from '../reducers/map'; -import { EmbeddableInput, EmbeddableOutput } from '../../../../../src/plugins/embeddable/public'; -import { Filter, Query, RefreshInterval, TimeRange } from '../../../../../src/plugins/data/common'; -import { LayerDescriptor, MapCenterAndZoom } from '../../common/descriptor_types'; +import { + EmbeddableInput, + EmbeddableOutput, + SavedObjectEmbeddableInput, +} from '../../../../../src/plugins/embeddable/public'; +import { RefreshInterval } from '../../../../../src/plugins/data/common'; +import { MapCenterAndZoom } from '../../common/descriptor_types'; +import { MapSavedObjectAttributes } from '../../common/map_saved_object_type'; export interface MapEmbeddableConfig { - description?: string; - editUrl?: string; - editApp?: string; - editPath?: string; - indexPatterns: IIndexPattern[]; editable: boolean; - title?: string; - layerList: LayerDescriptor[]; - settings?: MapSettings; } -export interface MapEmbeddableInput extends EmbeddableInput { - timeRange?: TimeRange; - filters: Filter[]; - query?: Query; - refreshConfig: RefreshInterval; - isLayerTOCOpen: boolean; +interface MapEmbeddableState { + refreshConfig?: RefreshInterval; + isLayerTOCOpen?: boolean; openTOCDetails?: string[]; disableTooltipControl?: boolean; disableInteractive?: boolean; @@ -38,7 +31,13 @@ export interface MapEmbeddableInput extends EmbeddableInput { hiddenLayers?: string[]; hideFilterActions?: boolean; } +export type MapByValueInput = { + attributes: MapSavedObjectAttributes; +} & EmbeddableInput & + MapEmbeddableState; +export type MapByReferenceInput = SavedObjectEmbeddableInput & MapEmbeddableState; +export type MapEmbeddableInput = MapByValueInput | MapByReferenceInput; -export interface MapEmbeddableOutput extends EmbeddableOutput { +export type MapEmbeddableOutput = EmbeddableOutput & { indexPatterns: IIndexPattern[]; -} +}; diff --git a/x-pack/plugins/maps/public/help_menu_util.js b/x-pack/plugins/maps/public/help_menu_util.js deleted file mode 100644 index 053caf6688309..0000000000000 --- a/x-pack/plugins/maps/public/help_menu_util.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 { getDocLinks, getCoreChrome } from './kibana_services'; - -export function addHelpMenuToAppChrome() { - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = getDocLinks(); - - getCoreChrome().setHelpExtension({ - appName: 'Maps', - links: [ - { - linkType: 'documentation', - href: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/maps.html`, - }, - { - linkType: 'github', - title: '[Maps]', - labels: ['Team:Geo'], - }, - ], - }); -} diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index 782c37a72d99b..4dcc9193420c0 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -77,3 +77,6 @@ export const getRegionmapLayers = () => _.get(getKibanaCommonConfig(), 'regionma export const getTilemap = () => _.get(getKibanaCommonConfig(), 'tilemap', []); export const getShareService = () => pluginsStart.share; + +export const getIsAllowByValueEmbeddables = () => + pluginsStart.dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables; diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts index 9fbe090633747..6795fbebbd86a 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts @@ -4,16 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AnyAction } from 'redux'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { IndexPatternsContract } from 'src/plugins/data/public/index_patterns'; import { AppMountParameters } from 'kibana/public'; -import { IndexPattern } from 'src/plugins/data/public'; import { Embeddable, IContainer } from '../../../../../src/plugins/embeddable/public'; import { LayerDescriptor } from '../../common/descriptor_types'; -import { MapStore, MapStoreState } from '../reducers/store'; -import { EventHandlers } from '../reducers/non_serializable_instances'; -import { RenderToolTipContent } from '../classes/tooltips/tooltip_property'; import { MapEmbeddableConfig, MapEmbeddableInput, MapEmbeddableOutput } from '../embeddable/types'; import { SourceRegistryEntry } from '../classes/sources/source_registry'; import { LayerWizard } from '../classes/layers/layer_wizard_registry'; @@ -21,25 +16,13 @@ import { LayerWizard } from '../classes/layers/layer_wizard_registry'; let loadModulesPromise: Promise; interface LazyLoadedMapModules { - getMapsSavedObjectLoader: any; MapEmbeddable: new ( config: MapEmbeddableConfig, initialInput: MapEmbeddableInput, - parent?: IContainer, - renderTooltipContent?: RenderToolTipContent, - eventHandlers?: EventHandlers + parent?: IContainer ) => Embeddable; getIndexPatternService: () => IndexPatternsContract; - getHttp: () => any; getMapsCapabilities: () => any; - createMapStore: () => MapStore; - addLayerWithoutDataSync: (layerDescriptor: LayerDescriptor) => AnyAction; - getQueryableUniqueIndexPatternIds: (state: MapStoreState) => string[]; - getInitialLayers: ( - layerListJSON?: string, - initialLayers?: LayerDescriptor[] - ) => LayerDescriptor[]; - mergeInputWithSavedMap: any; renderApp: (params: AppMountParameters) => Promise<() => void>; createSecurityLayerDescriptors: ( indexPatternId: string, @@ -47,7 +30,6 @@ interface LazyLoadedMapModules { ) => LayerDescriptor[]; registerLayerWizard: (layerWizard: LayerWizard) => void; registerSource(entry: SourceRegistryEntry): void; - getIndexPatternsFromIds: (indexPatternIds: string[]) => Promise; createTileMapLayerDescriptor: ({ label, mapType, @@ -95,41 +77,25 @@ export async function lazyLoadMapModules(): Promise { loadModulesPromise = new Promise(async (resolve) => { const { - getMapsSavedObjectLoader, - getQueryableUniqueIndexPatternIds, MapEmbeddable, getIndexPatternService, - getHttp, getMapsCapabilities, - createMapStore, - addLayerWithoutDataSync, - getInitialLayers, - mergeInputWithSavedMap, renderApp, createSecurityLayerDescriptors, registerLayerWizard, registerSource, - getIndexPatternsFromIds, createTileMapLayerDescriptor, createRegionMapLayerDescriptor, } = await import('./lazy'); resolve({ - getMapsSavedObjectLoader, - getQueryableUniqueIndexPatternIds, MapEmbeddable, getIndexPatternService, - getHttp, getMapsCapabilities, - createMapStore, - addLayerWithoutDataSync, - getInitialLayers, - mergeInputWithSavedMap, renderApp, createSecurityLayerDescriptors, registerLayerWizard, registerSource, - getIndexPatternsFromIds, createTileMapLayerDescriptor, createRegionMapLayerDescriptor, }); diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts index 067f213603fe3..c0d9b61bfc014 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts @@ -7,18 +7,11 @@ // These are map-dependencies of the embeddable. // By lazy-loading these, the Maps-app can register the embeddable when the plugin mounts, without actually pulling all the code. -export * from '../../routing/bootstrap/services/gis_map_saved_object_loader'; export * from '../../embeddable/map_embeddable'; export * from '../../kibana_services'; -export * from '../../reducers/store'; -export * from '../../actions'; -export * from '../../selectors/map_selectors'; -export * from '../../routing/bootstrap/get_initial_layers'; -export * from '../../embeddable/merge_input_with_saved_map'; -export { renderApp } from '../../routing/render_app'; +export { renderApp } from '../../render_app'; export * from '../../classes/layers/solution_layers/security'; export { registerLayerWizard } from '../../classes/layers/layer_wizard_registry'; export { registerSource } from '../../classes/sources/source_registry'; -export { getIndexPatternsFromIds } from '../../index_pattern_util'; export { createTileMapLayerDescriptor } from '../../classes/layers/create_tile_map_layer_descriptor'; export { createRegionMapLayerDescriptor } from '../../classes/layers/create_region_map_layer_descriptor'; diff --git a/x-pack/plugins/maps/public/map_attribute_service.ts b/x-pack/plugins/maps/public/map_attribute_service.ts new file mode 100644 index 0000000000000..0e3ef1b9ea518 --- /dev/null +++ b/x-pack/plugins/maps/public/map_attribute_service.ts @@ -0,0 +1,87 @@ +/* + * 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 { AttributeService } from '../../../../src/plugins/embeddable/public'; +import { MapSavedObjectAttributes } from '../common/map_saved_object_type'; +import { MAP_SAVED_OBJECT_TYPE } from '../common/constants'; +import { getMapEmbeddableDisplayName } from '../common/i18n_getters'; +import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/saved_objects/public'; +import { getCoreOverlays, getEmbeddableService, getSavedObjectsClient } from './kibana_services'; +// @ts-expect-error +import { extractReferences, injectReferences } from '../common/migrations/references'; +import { MapByValueInput, MapByReferenceInput } from './embeddable/types'; + +export type MapAttributeService = AttributeService< + MapSavedObjectAttributes, + MapByValueInput, + MapByReferenceInput +>; + +let mapAttributeService: MapAttributeService | null = null; + +export function getMapAttributeService(): MapAttributeService { + if (mapAttributeService) { + return mapAttributeService; + } + + mapAttributeService = getEmbeddableService().getAttributeService< + MapSavedObjectAttributes, + MapByValueInput, + MapByReferenceInput + >(MAP_SAVED_OBJECT_TYPE, { + saveMethod: async (attributes: MapSavedObjectAttributes, savedObjectId?: string) => { + const { attributes: attributesWithExtractedReferences, references } = extractReferences({ + attributes, + }); + + const savedObject = await (savedObjectId + ? getSavedObjectsClient().update( + MAP_SAVED_OBJECT_TYPE, + savedObjectId, + attributesWithExtractedReferences, + { references } + ) + : getSavedObjectsClient().create( + MAP_SAVED_OBJECT_TYPE, + attributesWithExtractedReferences, + { references } + )); + return { id: savedObject.id }; + }, + unwrapMethod: async (savedObjectId: string): Promise => { + const savedObject = await getSavedObjectsClient().get( + MAP_SAVED_OBJECT_TYPE, + savedObjectId + ); + + if (savedObject.error) { + throw savedObject.error; + } + + const { attributes } = injectReferences(savedObject); + return attributes; + }, + checkForDuplicateTitle: (props: OnSaveProps) => { + return checkForDuplicateTitle( + { + title: props.newTitle, + copyOnSave: false, + lastSavedTitle: '', + getEsType: () => MAP_SAVED_OBJECT_TYPE, + getDisplayName: getMapEmbeddableDisplayName, + }, + props.isTitleDuplicateConfirmed, + props.onTitleDuplicate, + { + savedObjectsClient: getSavedObjectsClient(), + overlays: getCoreOverlays(), + } + ); + }, + }); + + return mapAttributeService; +} diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index b79a2b06b9b37..3da346aaf4443 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -8,6 +8,7 @@ import { Setup as InspectorSetupContract } from 'src/plugins/inspector/public'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; +import { DashboardStart } from 'src/plugins/dashboard/public'; import { AppMountParameters, CoreSetup, @@ -84,6 +85,7 @@ export interface MapsPluginStartDependencies { share: SharePluginStart; visualizations: VisualizationsStart; savedObjects: SavedObjectsStart; + dashboard: DashboardStart; } /** diff --git a/x-pack/plugins/maps/public/reducers/ui.ts b/x-pack/plugins/maps/public/reducers/ui.ts index 2ea0798d1e768..04c3805902ef6 100644 --- a/x-pack/plugins/maps/public/reducers/ui.ts +++ b/x-pack/plugins/maps/public/reducers/ui.ts @@ -5,6 +5,8 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ +import { getMapsCapabilities } from '../kibana_services'; + import { UPDATE_FLYOUT, CLOSE_SET_VIEW, @@ -38,7 +40,7 @@ export const DEFAULT_IS_LAYER_TOC_OPEN = true; export const DEFAULT_MAP_UI_STATE = { flyoutDisplay: FLYOUT_STATE.NONE, isFullScreen: false, - isReadOnly: false, + isReadOnly: !getMapsCapabilities().save, isLayerTOCOpen: DEFAULT_IS_LAYER_TOC_OPEN, isSetViewOpen: false, // storing TOC detail visibility outside of map.layerList because its UI state and not map rendering state. diff --git a/x-pack/plugins/maps/public/render_app.tsx b/x-pack/plugins/maps/public/render_app.tsx new file mode 100644 index 0000000000000..68b74211ee273 --- /dev/null +++ b/x-pack/plugins/maps/public/render_app.tsx @@ -0,0 +1,139 @@ +/* + * 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 _ from 'lodash'; +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { Router, Switch, Route, Redirect, RouteComponentProps } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { AppMountParameters } from 'kibana/public'; +import { + getCoreChrome, + getCoreI18n, + getMapsCapabilities, + getToasts, + getEmbeddableService, + getDocLinks, +} from './kibana_services'; +import { + createKbnUrlStateStorage, + withNotifyOnErrors, + IKbnUrlStateStorage, +} from '../../../../src/plugins/kibana_utils/public'; +import { ListPage, MapPage } from './routes'; +import { MapByValueInput, MapByReferenceInput } from './embeddable/types'; + +export let goToSpecifiedPath: (path: string) => void; +export let kbnUrlStateStorage: IKbnUrlStateStorage; + +function setAppChrome() { + if (!getMapsCapabilities().save) { + getCoreChrome().setBadge({ + text: i18n.translate('xpack.maps.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('xpack.maps.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save maps', + }), + iconType: 'glasses', + }); + } + + const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = getDocLinks(); + + getCoreChrome().setHelpExtension({ + appName: 'Maps', + links: [ + { + linkType: 'documentation', + href: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/maps.html`, + }, + { + linkType: 'github', + title: '[Maps]', + labels: ['Team:Geo'], + }, + ], + }); +} + +export async function renderApp({ + element, + history, + onAppLeave, + setHeaderActionMenu, +}: AppMountParameters) { + goToSpecifiedPath = (path) => history.push(path); + kbnUrlStateStorage = createKbnUrlStateStorage({ + useHash: false, + history, + ...withNotifyOnErrors(getToasts()), + }); + + setAppChrome(); + + function renderMapApp(routeProps: RouteComponentProps<{ savedMapId?: string }>) { + const stateTransfer = getEmbeddableService()?.getStateTransfer( + history as AppMountParameters['history'] + ); + + const { embeddableId, originatingApp, valueInput } = + stateTransfer?.getIncomingEditorState({ keysToRemoveAfterFetch: ['originatingApp'] }) || {}; + + let mapEmbeddableInput; + if (routeProps.match.params.savedMapId) { + mapEmbeddableInput = { + savedObjectId: routeProps.match.params.savedMapId, + } as MapByReferenceInput; + } + if (valueInput) { + mapEmbeddableInput = valueInput as MapByValueInput; + } + + return ( + + ); + } + + const I18nContext = getCoreI18n().Context; + render( + + + + + + // Redirect other routes to list, or if hash-containing, their non-hash equivalents + { + if (hash) { + // Remove leading hash + const newPath = hash.substr(1); + return ; + } else if (pathname === '/' || pathname === '') { + return ; + } else { + return ; + } + }} + /> + + + , + element + ); + + return () => { + unmountComponentAtNode(element); + }; +} diff --git a/x-pack/plugins/maps/public/routing/store_operations.ts b/x-pack/plugins/maps/public/routes/index.ts similarity index 66% rename from x-pack/plugins/maps/public/routing/store_operations.ts rename to x-pack/plugins/maps/public/routes/index.ts index 53ebbb3328ff9..0244331c0730b 100644 --- a/x-pack/plugins/maps/public/routing/store_operations.ts +++ b/x-pack/plugins/maps/public/routes/index.ts @@ -4,8 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createMapStore } from '../reducers/store'; - -const store = createMapStore(); - -export const getStore = () => store; +export { MapPage, SavedMap } from './map_page'; +export { ListPage } from './list_page'; diff --git a/x-pack/plugins/maps/public/embeddable/merge_input_with_saved_map.d.ts b/x-pack/plugins/maps/public/routes/list_page/index.ts similarity index 57% rename from x-pack/plugins/maps/public/embeddable/merge_input_with_saved_map.d.ts rename to x-pack/plugins/maps/public/routes/list_page/index.ts index 4ce4df02f6a39..0c278ff1f8225 100644 --- a/x-pack/plugins/maps/public/embeddable/merge_input_with_saved_map.d.ts +++ b/x-pack/plugins/maps/public/routes/list_page/index.ts @@ -4,9 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MapEmbeddableInput } from './map_embeddable'; - -export function mergeInputWithSavedMap( - input: MapEmbeddableInput, - savedmap: unknown -): Partial; +export { LoadListAndRender as ListPage } from './load_list_and_render'; diff --git a/x-pack/plugins/maps/public/routing/routes/list/load_list_and_render.tsx b/x-pack/plugins/maps/public/routes/list_page/load_list_and_render.tsx similarity index 77% rename from x-pack/plugins/maps/public/routing/routes/list/load_list_and_render.tsx rename to x-pack/plugins/maps/public/routes/list_page/load_list_and_render.tsx index e85afb470dbe6..087fd82300ce3 100644 --- a/x-pack/plugins/maps/public/routing/routes/list/load_list_and_render.tsx +++ b/x-pack/plugins/maps/public/routes/list_page/load_list_and_render.tsx @@ -7,9 +7,9 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { Redirect } from 'react-router-dom'; -import { getMapsSavedObjectLoader } from '../../bootstrap/services/gis_map_saved_object_loader'; -import { getToasts } from '../../../kibana_services'; +import { getSavedObjectsClient, getToasts } from '../../kibana_services'; import { MapsListView } from './maps_list_view'; +import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; export class LoadListAndRender extends React.Component { _isMounted: boolean = false; @@ -29,9 +29,13 @@ export class LoadListAndRender extends React.Component { async _loadMapsList() { try { - const { hits = [] } = await getMapsSavedObjectLoader().find('', 1); + const results = await getSavedObjectsClient().find({ + type: MAP_SAVED_OBJECT_TYPE, + perPage: 1, + fields: ['title'], + }); if (this._isMounted) { - this.setState({ mapsLoaded: true, hasSavedMaps: !!hits.length }); + this.setState({ mapsLoaded: true, hasSavedMaps: !!results.savedObjects.length }); } } catch (err) { if (this._isMounted) { diff --git a/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.tsx b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx similarity index 90% rename from x-pack/plugins/maps/public/routing/routes/list/maps_list_view.tsx rename to x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx index ca92442ae93e6..97ed3d428d341 100644 --- a/x-pack/plugins/maps/public/routing/routes/list/maps_list_view.tsx +++ b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx @@ -31,18 +31,17 @@ import { } from '@elastic/eui/src/components/basic_table/basic_table'; import { EuiTableSortingType } from '@elastic/eui'; import { goToSpecifiedPath } from '../../render_app'; -// @ts-expect-error -import { addHelpMenuToAppChrome } from '../../../help_menu_util'; -import { APP_ID, MAP_PATH } from '../../../../common/constants'; +import { APP_ID, MAP_PATH, MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; import { getMapsCapabilities, getUiSettings, getToasts, getCoreChrome, getNavigateToApp, -} from '../../../kibana_services'; -import { getMapsSavedObjectLoader } from '../../bootstrap/services/gis_map_saved_object_loader'; -import { getAppTitle } from '../../../../common/i18n_getters'; + getSavedObjectsClient, +} from '../../kibana_services'; +import { getAppTitle } from '../../../common/i18n_getters'; +import { MapSavedObjectAttributes } from '../../../common/map_saved_object_type'; export const EMPTY_FILTER = ''; @@ -60,7 +59,7 @@ interface State { showDeleteModal: boolean; showLimitError: boolean; filter: string; - items: SelectionItem[]; + items: TableRow[]; selectedIds: string[]; page: number; perPage: number; @@ -69,8 +68,10 @@ interface State { totalItems?: number; } -interface SelectionItem { +interface TableRow { id: string; + title: string; + description?: string; } export class MapsListView extends React.Component { @@ -101,17 +102,20 @@ export class MapsListView extends React.Component { async initMapList() { this.fetchItems(); - addHelpMenuToAppChrome(); getCoreChrome().docTitle.change(getAppTitle()); getCoreChrome().setBreadcrumbs([{ text: getAppTitle() }]); } - _find = (search: string) => getMapsSavedObjectLoader().find(search, this.state.listingLimit); - - _delete = (ids: string[]) => getMapsSavedObjectLoader().delete(ids); - debouncedFetch = _.debounce(async (filter) => { - const response = await this._find(filter); + const response = await getSavedObjectsClient().find({ + type: MAP_SAVED_OBJECT_TYPE, + search: filter ? `${filter}*` : undefined, + perPage: this.state.listingLimit, + page: 1, + searchFields: ['title^3', 'description'], + defaultSearchOperator: 'AND', + fields: ['description', 'title'], + }); if (!this._isMounted) { return; @@ -123,7 +127,13 @@ export class MapsListView extends React.Component { this.setState({ hasInitialFetchReturned: true, isFetchingItems: false, - items: response.hits, + items: response.savedObjects.map((savedObject) => { + return { + id: savedObject.id, + title: savedObject.attributes.title, + description: savedObject.attributes.description, + }; + }), totalItems: response.total, showLimitError: response.total > this.state.listingLimit, }); @@ -141,7 +151,10 @@ export class MapsListView extends React.Component { deleteSelectedItems = async () => { try { - await this._delete(this.state.selectedIds); + const deletions = this.state.selectedIds.map((id) => { + return getSavedObjectsClient().delete(MAP_SAVED_OBJECT_TYPE, id); + }); + await Promise.all(deletions); } catch (error) { getToasts().addDanger({ title: i18n.translate('xpack.maps.mapListing.unableToDeleteToastTitle', { @@ -165,7 +178,7 @@ export class MapsListView extends React.Component { this.setState({ showDeleteModal: true }); }; - onTableChange = ({ page, sort }: CriteriaWithPagination) => { + onTableChange = ({ page, sort }: CriteriaWithPagination) => { const { index: pageIndex, size: pageSize } = page; let { field: sortField, direction: sortDirection } = sort || {}; @@ -361,7 +374,7 @@ export class MapsListView extends React.Component { { e.preventDefault(); - goToSpecifiedPath(`/map/${record.id}`); + goToSpecifiedPath(`/${MAP_PATH}/${record.id}`); }} data-test-subj={`mapListingTitleLink-${record.title.split(' ').join('-')}`} > @@ -388,7 +401,7 @@ export class MapsListView extends React.Component { let selection; if (!this.state.readOnly) { selection = { - onSelectionChange: (s: SelectionItem[]) => { + onSelectionChange: (s: TableRow[]) => { this.setState({ selectedIds: s.map((item) => { return item.id; diff --git a/x-pack/plugins/maps/public/routes/map_page/index.ts b/x-pack/plugins/maps/public/routes/map_page/index.ts new file mode 100644 index 0000000000000..f7bea5f2c6b7f --- /dev/null +++ b/x-pack/plugins/maps/public/routes/map_page/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { SavedMap } from './saved_map'; +export { MapPage } from './map_page'; diff --git a/x-pack/plugins/maps/public/routing/routes/map_app/index.ts b/x-pack/plugins/maps/public/routes/map_page/map_app/index.ts similarity index 64% rename from x-pack/plugins/maps/public/routing/routes/map_app/index.ts rename to x-pack/plugins/maps/public/routes/map_page/map_app/index.ts index 0b9f0cfe33e44..5c5031bb6a24e 100644 --- a/x-pack/plugins/maps/public/routing/routes/map_app/index.ts +++ b/x-pack/plugins/maps/public/routes/map_page/map_app/index.ts @@ -17,32 +17,12 @@ import { getRefreshConfig, getTimeFilters, hasDirtyState, - getLayerListConfigOnly, } from '../../../selectors/map_selectors'; -import { - replaceLayerList, - setGotoWithCenter, - setIsLayerTOCOpen, - setMapSettings, - setOpenTOCDetails, - setQuery, - setReadOnly, - setRefreshConfig, - setSelectedLayer, - updateFlyout, - enableFullScreen, - openMapSettings, -} from '../../../actions'; +import { setQuery, setRefreshConfig, enableFullScreen, openMapSettings } from '../../../actions'; import { FLYOUT_STATE } from '../../../reducers/ui'; -import { getMapsCapabilities } from '../../../kibana_services'; import { getInspectorAdapters } from '../../../reducers/non_serializable_instances'; import { MapStoreState } from '../../../reducers/store'; -import { - MapRefreshConfig, - MapCenterAndZoom, - LayerDescriptor, -} from '../../../../common/descriptor_types'; -import { MapSettings } from '../../../reducers/map'; +import { MapRefreshConfig } from '../../../../common/descriptor_types'; function mapStateToProps(state: MapStoreState) { return { @@ -54,7 +34,6 @@ function mapStateToProps(state: MapStoreState) { flyoutDisplay: getFlyoutDisplay(state), refreshConfig: getRefreshConfig(state), filters: getFilters(state), - layerListConfigOnly: getLayerListConfigOnly(state), query: getQuery(state), timeFilters: getTimeFilters(state), }; @@ -84,16 +63,6 @@ function mapDispatchToProps(dispatch: ThunkDispatch dispatch(setRefreshConfig(refreshConfig)), - replaceLayerList: (layerList: LayerDescriptor[]) => dispatch(replaceLayerList(layerList)), - setGotoWithCenter: (latLonZoom: MapCenterAndZoom) => dispatch(setGotoWithCenter(latLonZoom)), - setMapSettings: (mapSettings: MapSettings) => dispatch(setMapSettings(mapSettings)), - setIsLayerTOCOpen: (isLayerTOCOpen: boolean) => dispatch(setIsLayerTOCOpen(isLayerTOCOpen)), - setOpenTOCDetails: (openTOCDetails: string[]) => dispatch(setOpenTOCDetails(openTOCDetails)), - clearUi: () => { - dispatch(setSelectedLayer(null)); - dispatch(updateFlyout(FLYOUT_STATE.NONE)); - dispatch(setReadOnly(!getMapsCapabilities().save)); - }, enableFullScreen: () => dispatch(enableFullScreen()), openMapSettings: () => dispatch(openMapSettings()), }; diff --git a/x-pack/plugins/maps/public/routing/routes/map_app/map_app.tsx b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx similarity index 65% rename from x-pack/plugins/maps/public/routing/routes/map_app/map_app.tsx rename to x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx index a7aefcad3aeda..817fbf3656103 100644 --- a/x-pack/plugins/maps/public/routing/routes/map_app/map_app.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx @@ -9,9 +9,8 @@ import 'mapbox-gl/dist/mapbox-gl.css'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { AppLeaveAction, AppMountParameters } from 'kibana/public'; -import { EmbeddableStateTransfer, Adapters } from 'src/plugins/embeddable/public'; +import { Adapters } from 'src/plugins/embeddable/public'; import { Subscription } from 'rxjs'; -import { DEFAULT_IS_LAYER_TOC_OPEN } from '../../../reducers/ui'; import { getData, getCoreChrome, @@ -19,19 +18,14 @@ import { getNavigation, getToasts, } from '../../../kibana_services'; -import { copyPersistentState } from '../../../reducers/util'; -import { getInitialLayers, getInitialLayersFromUrlParam } from '../../bootstrap/get_initial_layers'; -import { getInitialTimeFilters } from '../../bootstrap/get_initial_time_filters'; -import { getInitialRefreshConfig } from '../../bootstrap/get_initial_refresh_config'; -import { getInitialQuery } from '../../bootstrap/get_initial_query'; import { + AppStateManager, + startAppStateSyncing, getGlobalState, updateGlobalState, startGlobalStateSyncing, MapsGlobalState, -} from '../../state_syncing/global_sync'; -import { AppStateManager } from '../../state_syncing/app_state_manager'; -import { startAppStateSyncing } from '../../state_syncing/app_sync'; +} from '../url_state'; import { esFilters, Filter, @@ -44,26 +38,25 @@ import { } from '../../../../../../../src/plugins/data/public'; import { MapContainer } from '../../../connected_components/map_container'; import { getIndexPatternsFromIds } from '../../../index_pattern_util'; -import { getTopNavConfig } from './top_nav_config'; -import { getBreadcrumbs, unsavedChangesTitle, unsavedChangesWarning } from './get_breadcrumbs'; +import { getTopNavConfig } from '../top_nav_config'; +import { MapRefreshConfig, MapQuery } from '../../../../common/descriptor_types'; +import { goToSpecifiedPath } from '../../../render_app'; +import { MapSavedObjectAttributes } from '../../../../common/map_saved_object_type'; +import { getExistingMapPath } from '../../../../common/constants'; import { - LayerDescriptor, - MapRefreshConfig, - MapCenterAndZoom, - MapQuery, -} from '../../../../common/descriptor_types'; -import { MapSettings } from '../../../reducers/map'; -import { ISavedGisMap } from '../../bootstrap/services/saved_gis_map'; -import { getMapsSavedObjectLoader } from '../../bootstrap/services/gis_map_saved_object_loader'; -import { goToSpecifiedPath } from '../../render_app'; + getInitialQuery, + getInitialRefreshConfig, + getInitialTimeFilters, + SavedMap, + unsavedChangesTitle, + unsavedChangesWarning, +} from '../saved_map'; interface Props { - savedMapId?: string; + savedMap: SavedMap; + // saveCounter used to trigger MapApp render after SaveMap.save + saveCounter: number; onAppLeave: AppMountParameters['onAppLeave']; - stateTransfer: EmbeddableStateTransfer; - originatingApp?: string; - layerListConfigOnly: LayerDescriptor[]; - replaceLayerList: (layerList: LayerDescriptor[]) => void; filters: Filter[]; isFullScreen: boolean; isOpenSettingsDisabled: boolean; @@ -86,22 +79,14 @@ interface Props { refreshConfig: MapRefreshConfig; setRefreshConfig: (refreshConfig: MapRefreshConfig) => void; isSaveDisabled: boolean; - clearUi: () => void; - setGotoWithCenter: (latLonZoom: MapCenterAndZoom) => void; - setMapSettings: (mapSettings: MapSettings) => void; - setIsLayerTOCOpen: (isLayerTOCOpen: boolean) => void; - setOpenTOCDetails: (openTOCDetails: string[]) => void; query: MapQuery | undefined; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; } interface State { initialized: boolean; - initialLayerListConfig?: LayerDescriptor[]; indexPatterns: IndexPattern[]; savedQuery?: SavedQuery; - originatingApp?: string; - savedMap?: ISavedGisMap; } export class MapApp extends React.Component { @@ -117,8 +102,6 @@ export class MapApp extends React.Component { this.state = { indexPatterns: [], initialized: false, - // tracking originatingApp in state so the connection can be broken by users - originatingApp: props.originatingApp, }; } @@ -140,7 +123,7 @@ export class MapApp extends React.Component { this._initMap(); this.props.onAppLeave((actions) => { - if (this._hasUnsavedChanges()) { + if (this.props.savedMap.hasUnsavedChanges()) { return actions.confirm(unsavedChangesWarning, unsavedChangesTitle); } return actions.default() as AppLeaveAction; @@ -164,34 +147,10 @@ export class MapApp extends React.Component { this._globalSyncChangeMonitorSubscription.unsubscribe(); } - getCoreChrome().setBreadcrumbs([]); - } - - _hasUnsavedChanges = () => { - if (!this.state.savedMap) { - return false; - } - - const savedLayerList = this.state.savedMap.getLayerList(); - return !savedLayerList - ? !_.isEqual(this.props.layerListConfigOnly, this.state.initialLayerListConfig) - : // savedMap stores layerList as a JSON string using JSON.stringify. - // JSON.stringify removes undefined properties from objects. - // savedMap.getLayerList converts the JSON string back into Javascript array of objects. - // Need to perform the same process for layerListConfigOnly to compare apples to apples - // and avoid undefined properties in layerListConfigOnly triggering unsaved changes. - !_.isEqual(JSON.parse(JSON.stringify(this.props.layerListConfigOnly)), savedLayerList); - }; - - _setBreadcrumbs = (title: string) => { - const breadcrumbs = getBreadcrumbs({ - title, - getHasUnsavedChanges: this._hasUnsavedChanges, - originatingApp: this.state.originatingApp, - getAppNameFromId: this.props.stateTransfer.getAppNameFromId, + this.props.onAppLeave((actions) => { + return actions.default(); }); - getCoreChrome().setBreadcrumbs(breadcrumbs); - }; + } _updateFromGlobalState = ({ changes, @@ -262,12 +221,12 @@ export class MapApp extends React.Component { updateGlobalState(updatedGlobalState, !this.state.initialized); }; - _initMapAndLayerSettings(savedMap: ISavedGisMap) { + _initMapAndLayerSettings(mapSavedObjectAttributes: MapSavedObjectAttributes) { const globalState: MapsGlobalState = getGlobalState(); let savedObjectFilters = []; - if (savedMap.mapStateJSON) { - const mapState = JSON.parse(savedMap.mapStateJSON); + if (mapSavedObjectAttributes.mapStateJSON) { + const mapState = JSON.parse(mapSavedObjectAttributes.mapStateJSON); if (mapState.filters) { savedObjectFilters = mapState.filters; } @@ -275,7 +234,7 @@ export class MapApp extends React.Component { const appFilters = this._appStateManager.getFilters() || []; const query = getInitialQuery({ - mapStateJSON: savedMap.mapStateJSON, + mapStateJSON: mapSavedObjectAttributes.mapStateJSON, appState: this._appStateManager.getAppState(), }); if (query) { @@ -286,23 +245,17 @@ export class MapApp extends React.Component { filters: [..._.get(globalState, 'filters', []), ...appFilters, ...savedObjectFilters], query, time: getInitialTimeFilters({ - mapStateJSON: savedMap.mapStateJSON, + mapStateJSON: mapSavedObjectAttributes.mapStateJSON, globalState, }), }); this._onRefreshConfigChange( getInitialRefreshConfig({ - mapStateJSON: savedMap.mapStateJSON, + mapStateJSON: mapSavedObjectAttributes.mapStateJSON, globalState, }) ); - - const layerList = getInitialLayers(savedMap.layerListJSON, getInitialLayersFromUrlParam()); - this.props.replaceLayerList(layerList); - this.setState({ - initialLayerListConfig: copyPersistentState(layerList), - }); } _onFiltersChange = (filters: Filter[]) => { @@ -348,10 +301,9 @@ export class MapApp extends React.Component { }); }; - async _loadSavedMap(): Promise { - let savedMap: ISavedGisMap | null = null; + async _initMap() { try { - savedMap = await getMapsSavedObjectLoader().get(this.props.savedMapId); + await this.props.savedMap.whenReady(); } catch (err) { if (this._isMounted) { getToasts().addWarning({ @@ -362,66 +314,41 @@ export class MapApp extends React.Component { }); goToSpecifiedPath('/'); } - } - - return savedMap; - } - - async _initMap() { - const savedMap = await this._loadSavedMap(); - if (!this._isMounted || !savedMap) { return; } - this._setBreadcrumbs(savedMap.title); - getCoreChrome().docTitle.change(savedMap.title); - if (this.props.savedMapId) { - getCoreChrome().recentlyAccessed.add(savedMap.getFullPath(), savedMap.title, savedMap.id!); + if (!this._isMounted) { + return; } - this._initMapAndLayerSettings(savedMap); - - this.props.clearUi(); - - if (savedMap.mapStateJSON) { - const mapState = JSON.parse(savedMap.mapStateJSON); - this.props.setGotoWithCenter({ - lat: mapState.center.lat, - lon: mapState.center.lon, - zoom: mapState.zoom, - }); - if (mapState.settings) { - this.props.setMapSettings(mapState.settings); - } + this.props.savedMap.setBreadcrumbs(); + getCoreChrome().docTitle.change(this.props.savedMap.getTitle()); + const savedObjectId = this.props.savedMap.getSavedObjectId(); + if (savedObjectId) { + getCoreChrome().recentlyAccessed.add( + getExistingMapPath(savedObjectId), + this.props.savedMap.getTitle(), + savedObjectId + ); } - if (savedMap.uiStateJSON) { - const uiState = JSON.parse(savedMap.uiStateJSON); - this.props.setIsLayerTOCOpen(_.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN)); - this.props.setOpenTOCDetails(_.get(uiState, 'openTOCDetails', [])); - } + this._initMapAndLayerSettings(this.props.savedMap.getAttributes()); - this.setState({ initialized: true, savedMap }); + this.setState({ initialized: true }); } _renderTopNav() { - if (this.props.isFullScreen || !this.state.savedMap) { + if (this.props.isFullScreen) { return null; } const topNavConfig = getTopNavConfig({ - savedMap: this.state.savedMap, + savedMap: this.props.savedMap, isOpenSettingsDisabled: this.props.isOpenSettingsDisabled, isSaveDisabled: this.props.isSaveDisabled, enableFullScreen: this.props.enableFullScreen, openMapSettings: this.props.openMapSettings, inspectorAdapters: this.props.inspectorAdapters, - setBreadcrumbs: this._setBreadcrumbs, - stateTransfer: this.props.stateTransfer, - originatingApp: this.state.originatingApp, - cutOriginatingAppConnection: () => { - this.setState({ originatingApp: undefined }); - }, }); const { TopNavMenu } = getNavigation().ui; @@ -485,7 +412,7 @@ export class MapApp extends React.Component { }; render() { - if (!this.state.initialized || !this.state.savedMap) { + if (!this.state.initialized) { return null; } @@ -496,8 +423,8 @@ export class MapApp extends React.Component {
diff --git a/x-pack/plugins/maps/public/routes/map_page/map_page.tsx b/x-pack/plugins/maps/public/routes/map_page/map_page.tsx new file mode 100644 index 0000000000000..c8c9f620845ee --- /dev/null +++ b/x-pack/plugins/maps/public/routes/map_page/map_page.tsx @@ -0,0 +1,79 @@ +/* + * 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 React, { Component } from 'react'; +import { Provider } from 'react-redux'; +import { AppMountParameters } from 'kibana/public'; +import { EmbeddableStateTransfer } from 'src/plugins/embeddable/public'; +import { MapApp } from './map_app'; +import { SavedMap, getInitialLayersFromUrlParam } from './saved_map'; +import { MapEmbeddableInput } from '../../embeddable/types'; + +interface Props { + mapEmbeddableInput?: MapEmbeddableInput; + embeddableId?: string; + onAppLeave: AppMountParameters['onAppLeave']; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; + stateTransfer: EmbeddableStateTransfer; + originatingApp?: string; +} + +interface State { + savedMap: SavedMap; + saveCounter: number; +} + +// react-router-dom.route "render" method may be called multiple times for the same route. +// Therefore state can not exist in the "render" closure +// MapAppContainer exists to wrap MapApp in a component so that a single instance of SavedMap +// exists per route regardless of how many times render method is called. +export class MapPage extends Component { + private _isMounted: boolean = false; + + constructor(props: Props) { + super(props); + this.state = { + savedMap: new SavedMap({ + defaultLayers: getInitialLayersFromUrlParam(), + mapEmbeddableInput: props.mapEmbeddableInput, + embeddableId: props.embeddableId, + originatingApp: props.originatingApp, + stateTransfer: props.stateTransfer, + onSaveCallback: this.updateSaveCounter, + }), + saveCounter: 0, + }; + } + + componentDidMount() { + this._isMounted = true; + } + + componentWillUnmount() { + this._isMounted = false; + } + + updateSaveCounter = () => { + if (this._isMounted) { + this.setState((prevState) => { + return { saveCounter: prevState.saveCounter + 1 }; + }); + } + }; + + render() { + return ( + + + + ); + } +} diff --git a/x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.test.tsx b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_breadcrumbs.test.tsx similarity index 59% rename from x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.test.tsx rename to x-pack/plugins/maps/public/routes/map_page/saved_map/get_breadcrumbs.test.tsx index 3516daf526968..64726cff942a3 100644 --- a/x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.test.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_breadcrumbs.test.tsx @@ -7,22 +7,42 @@ import { getBreadcrumbs } from './get_breadcrumbs'; jest.mock('../../../kibana_services', () => {}); -jest.mock('../../render_app', () => {}); +jest.mock('../../../render_app', () => {}); const getHasUnsavedChanges = () => { return false; }; test('should get breadcrumbs "Maps / mymap"', () => { - const breadcrumbs = getBreadcrumbs({ title: 'mymap', getHasUnsavedChanges }); + const breadcrumbs = getBreadcrumbs({ + pageTitle: 'mymap', + getHasUnsavedChanges, + isByValue: false, + }); expect(breadcrumbs.length).toBe(2); expect(breadcrumbs[0].text).toBe('Maps'); expect(breadcrumbs[1].text).toBe('mymap'); }); -test('should get breadcrumbs "Dashboard / Maps / mymap" with originatingApp', () => { +test('should get breadcrumbs "Dashboard / mymap" with originatingApp and by value', () => { + const breadcrumbs = getBreadcrumbs({ + pageTitle: 'mymap', + isByValue: true, + getHasUnsavedChanges, + originatingApp: 'dashboardId', + getAppNameFromId: (appId) => { + return 'Dashboard'; + }, + }); + expect(breadcrumbs.length).toBe(2); + expect(breadcrumbs[0].text).toBe('Dashboard'); + expect(breadcrumbs[1].text).toBe('mymap'); +}); + +test('should get breadcrumbs "Dashboard / Maps / mymap" with originatingApp and not by value', () => { const breadcrumbs = getBreadcrumbs({ - title: 'mymap', + pageTitle: 'mymap', + isByValue: false, getHasUnsavedChanges, originatingApp: 'dashboardId', getAppNameFromId: (appId) => { diff --git a/x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.tsx b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_breadcrumbs.tsx similarity index 67% rename from x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.tsx rename to x-pack/plugins/maps/public/routes/map_page/saved_map/get_breadcrumbs.tsx index 88dba0f83ec2f..7f3d82c6eaab3 100644 --- a/x-pack/plugins/maps/public/routing/routes/map_app/get_breadcrumbs.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_breadcrumbs.tsx @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { getCoreOverlays, getNavigateToApp } from '../../../kibana_services'; -import { goToSpecifiedPath } from '../../render_app'; +import { goToSpecifiedPath } from '../../../render_app'; import { getAppTitle } from '../../../../common/i18n_getters'; export const unsavedChangesWarning = i18n.translate( @@ -21,17 +21,20 @@ export const unsavedChangesTitle = i18n.translate('xpack.maps.breadCrumbs.unsave }); export function getBreadcrumbs({ - title, + pageTitle, + isByValue, getHasUnsavedChanges, originatingApp, getAppNameFromId, }: { - title: string; + pageTitle: string; + isByValue: boolean; getHasUnsavedChanges: () => boolean; originatingApp?: string; getAppNameFromId?: (id: string) => string | undefined; }) { const breadcrumbs = []; + if (originatingApp && getAppNameFromId) { breadcrumbs.push({ onClick: () => { @@ -41,24 +44,26 @@ export function getBreadcrumbs({ }); } - breadcrumbs.push({ - text: getAppTitle(), - onClick: async () => { - if (getHasUnsavedChanges()) { - const confirmed = await getCoreOverlays().openConfirm(unsavedChangesWarning, { - title: unsavedChangesTitle, - 'data-test-subj': 'appLeaveConfirmModal', - }); - if (confirmed) { + if (!isByValue) { + breadcrumbs.push({ + text: getAppTitle(), + onClick: async () => { + if (getHasUnsavedChanges()) { + const confirmed = await getCoreOverlays().openConfirm(unsavedChangesWarning, { + title: unsavedChangesTitle, + 'data-test-subj': 'appLeaveConfirmModal', + }); + if (confirmed) { + goToSpecifiedPath('/'); + } + } else { goToSpecifiedPath('/'); } - } else { - goToSpecifiedPath('/'); - } - }, - }); + }, + }); + } - breadcrumbs.push({ text: title }); + breadcrumbs.push({ text: pageTitle }); return breadcrumbs; } diff --git a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.test.js b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_layers.test.js similarity index 83% rename from x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.test.js rename to x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_layers.test.js index 66adb1da6900e..7779475668fe3 100644 --- a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.test.js +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_layers.test.js @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../meta', () => { +jest.mock('../../../meta', () => { return {}; }); -jest.mock('../../kibana_services'); +jest.mock('../../../kibana_services'); import { getInitialLayers } from './get_initial_layers'; @@ -15,7 +15,7 @@ const layerListNotProvided = undefined; describe('Saved object has layer list', () => { beforeEach(() => { - require('../../kibana_services').getEMSSettings = () => { + require('../../../kibana_services').getEMSSettings = () => { return { isEMSEnabled: () => true, }; @@ -36,7 +36,7 @@ describe('Saved object has layer list', () => { describe('kibana.yml configured with map.tilemap.url', () => { beforeAll(() => { - require('../../meta').getKibanaTileMap = () => { + require('../../../meta').getKibanaTileMap = () => { return { url: 'myTileUrl', }; @@ -66,15 +66,15 @@ describe('kibana.yml configured with map.tilemap.url', () => { describe('EMS is enabled', () => { beforeAll(() => { - require('../../meta').getKibanaTileMap = () => { + require('../../../meta').getKibanaTileMap = () => { return null; }; - require('../../kibana_services').getEMSSettings = () => { + require('../../../kibana_services').getEMSSettings = () => { return { isEMSEnabled: () => true, }; }; - require('../../kibana_services').getEmsTileLayerId = () => ({ + require('../../../kibana_services').getEmsTileLayerId = () => ({ bright: 'road_map', desaturated: 'road_map_desaturated', dark: 'dark_map', @@ -106,10 +106,10 @@ describe('EMS is enabled', () => { describe('EMS is not enabled', () => { beforeAll(() => { - require('../../meta').getKibanaTileMap = () => { + require('../../../meta').getKibanaTileMap = () => { return null; }; - require('../../kibana_services').getEMSSettings = () => { + require('../../../kibana_services').getEMSSettings = () => { return { isEMSEnabled: () => false, }; diff --git a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_layers.ts similarity index 69% rename from x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.ts rename to x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_layers.ts index c887320873995..b64f778d062cb 100644 --- a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_layers.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_layers.ts @@ -7,30 +7,30 @@ import _ from 'lodash'; import rison from 'rison-node'; import { i18n } from '@kbn/i18n'; -// Import each layer type, even those not used, to init in registry -import '../../classes/sources/wms_source'; -import '../../classes/sources/ems_file_source'; -import '../../classes/sources/es_search_source'; -import '../../classes/sources/es_pew_pew_source'; -import '../../classes/sources/kibana_regionmap_source'; -import '../../classes/sources/es_geo_grid_source'; -import '../../classes/sources/xyz_tms_source'; -import { LayerDescriptor } from '../../../common/descriptor_types'; +import '../../../classes/sources/wms_source'; +import '../../../classes/sources/ems_file_source'; +import '../../../classes/sources/es_search_source'; +import '../../../classes/sources/es_pew_pew_source'; +import '../../../classes/sources/kibana_regionmap_source'; +import '../../../classes/sources/es_geo_grid_source'; +import '../../../classes/sources/xyz_tms_source'; +import { LayerDescriptor } from '../../../../common/descriptor_types'; // @ts-expect-error -import { KibanaTilemapSource } from '../../classes/sources/kibana_tilemap_source'; -import { TileLayer } from '../../classes/layers/tile_layer/tile_layer'; +import { KibanaTilemapSource } from '../../../classes/sources/kibana_tilemap_source'; +import { TileLayer } from '../../../classes/layers/tile_layer/tile_layer'; // @ts-expect-error -import { EMSTMSSource } from '../../classes/sources/ems_tms_source'; +import { EMSTMSSource } from '../../../classes/sources/ems_tms_source'; // @ts-expect-error -import { VectorTileLayer } from '../../classes/layers/vector_tile_layer/vector_tile_layer'; -import { getEMSSettings, getToasts } from '../../kibana_services'; -import { INITIAL_LAYERS_KEY } from '../../../common/constants'; -import { getKibanaTileMap } from '../../meta'; +import { VectorTileLayer } from '../../../classes/layers/vector_tile_layer/vector_tile_layer'; +import { getEMSSettings, getToasts } from '../../../kibana_services'; +import { INITIAL_LAYERS_KEY } from '../../../../common/constants'; +import { getKibanaTileMap } from '../../../meta'; export function getInitialLayers(layerListJSON?: string, initialLayers: LayerDescriptor[] = []) { if (layerListJSON) { return JSON.parse(layerListJSON); } + const tilemapSourceFromKibana = getKibanaTileMap(); if (_.get(tilemapSourceFromKibana, 'url')) { const layerDescriptor = TileLayer.createDescriptor({ diff --git a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_query.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_query.ts similarity index 84% rename from x-pack/plugins/maps/public/routing/bootstrap/get_initial_query.ts rename to x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_query.ts index 43293d152dbff..85fe1950d9c2d 100644 --- a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_query.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_query.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getData } from '../../kibana_services'; -import { MapsAppState } from '../state_syncing/app_state_manager'; +import { getData } from '../../../kibana_services'; +import { MapsAppState } from '../url_state'; export function getInitialQuery({ mapStateJSON, diff --git a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_refresh_config.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_refresh_config.ts similarity index 87% rename from x-pack/plugins/maps/public/routing/bootstrap/get_initial_refresh_config.ts rename to x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_refresh_config.ts index 7d759cb25052f..a9c52beb7d906 100644 --- a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_refresh_config.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_refresh_config.ts @@ -5,8 +5,8 @@ */ import { QueryState } from 'src/plugins/data/public'; -import { getUiSettings } from '../../kibana_services'; -import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; +import { getUiSettings } from '../../../kibana_services'; +import { UI_SETTINGS } from '../../../../../../../src/plugins/data/public'; export function getInitialRefreshConfig({ mapStateJSON, diff --git a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_time_filters.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_time_filters.ts similarity index 92% rename from x-pack/plugins/maps/public/routing/bootstrap/get_initial_time_filters.ts rename to x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_time_filters.ts index 549cc154fe487..93197bb88dc28 100644 --- a/x-pack/plugins/maps/public/routing/bootstrap/get_initial_time_filters.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/get_initial_time_filters.ts @@ -5,7 +5,7 @@ */ import { QueryState } from 'src/plugins/data/public'; -import { getUiSettings } from '../../kibana_services'; +import { getUiSettings } from '../../../kibana_services'; export function getInitialTimeFilters({ mapStateJSON, diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/index.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/index.ts new file mode 100644 index 0000000000000..e9347dd986da7 --- /dev/null +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/index.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +export { SavedMap } from './saved_map'; +export { getInitialLayersFromUrlParam } from './get_initial_layers'; +export { getInitialQuery } from './get_initial_query'; +export { getInitialRefreshConfig } from './get_initial_refresh_config'; +export { getInitialTimeFilters } from './get_initial_time_filters'; +export { unsavedChangesTitle, unsavedChangesWarning } from './get_breadcrumbs'; 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 new file mode 100644 index 0000000000000..036f8cf11d374 --- /dev/null +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts @@ -0,0 +1,344 @@ +/* + * 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 _ from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { EmbeddableStateTransfer } from 'src/plugins/embeddable/public'; +import { MapSavedObjectAttributes } from '../../../../common/map_saved_object_type'; +import { MAP_PATH, MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants'; +import { createMapStore, MapStore, MapStoreState } from '../../../reducers/store'; +import { + getTimeFilters, + getMapZoom, + getMapCenter, + getLayerListRaw, + getRefreshConfig, + getQuery, + getFilters, + getMapSettings, + getLayerListConfigOnly, +} from '../../../selectors/map_selectors'; +import { + setGotoWithCenter, + setMapSettings, + replaceLayerList, + setIsLayerTOCOpen, + setOpenTOCDetails, + setHiddenLayers, +} from '../../../actions'; +import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../../selectors/ui_selectors'; +import { getMapAttributeService } from '../../../map_attribute_service'; +import { OnSaveProps } from '../../../../../../../src/plugins/saved_objects/public'; +import { MapByReferenceInput, MapEmbeddableInput } from '../../../embeddable/types'; +import { getCoreChrome, getToasts, getIsAllowByValueEmbeddables } from '../../../kibana_services'; +import { goToSpecifiedPath } from '../../../render_app'; +import { LayerDescriptor } from '../../../../common/descriptor_types'; +import { getInitialLayers } from './get_initial_layers'; +import { copyPersistentState } from '../../../reducers/util'; +import { getBreadcrumbs } from './get_breadcrumbs'; +import { DEFAULT_IS_LAYER_TOC_OPEN } from '../../../reducers/ui'; + +export class SavedMap { + private _attributes: MapSavedObjectAttributes | null = null; + private readonly _defaultLayers: LayerDescriptor[]; + private readonly _embeddableId?: string; + private _initialLayerListConfig: LayerDescriptor[] = []; + private _mapEmbeddableInput?: MapEmbeddableInput; + private readonly _onSaveCallback?: () => void; + private _originatingApp?: string; + private readonly _stateTransfer?: EmbeddableStateTransfer; + private readonly _store: MapStore; + + constructor({ + defaultLayers = [], + mapEmbeddableInput, + embeddableId, + onSaveCallback, + originatingApp, + stateTransfer, + }: { + defaultLayers?: LayerDescriptor[]; + mapEmbeddableInput?: MapEmbeddableInput; + embeddableId?: string; + onSaveCallback?: () => void; + originatingApp?: string; + stateTransfer?: EmbeddableStateTransfer; + }) { + this._defaultLayers = defaultLayers; + this._mapEmbeddableInput = mapEmbeddableInput; + this._embeddableId = embeddableId; + this._onSaveCallback = onSaveCallback; + this._originatingApp = originatingApp; + this._stateTransfer = stateTransfer; + this._store = createMapStore(); + } + + public getStore() { + return this._store; + } + + async whenReady() { + if (!this._mapEmbeddableInput) { + this._attributes = { + title: '', + description: '', + }; + } else { + this._attributes = await getMapAttributeService().unwrapAttributes(this._mapEmbeddableInput); + } + + if (this._attributes?.mapStateJSON) { + const mapState = JSON.parse(this._attributes.mapStateJSON); + if (mapState.settings) { + this._store.dispatch(setMapSettings(mapState.settings)); + } + } + + let isLayerTOCOpen = DEFAULT_IS_LAYER_TOC_OPEN; + if (this._mapEmbeddableInput && this._mapEmbeddableInput.isLayerTOCOpen !== undefined) { + isLayerTOCOpen = this._mapEmbeddableInput.isLayerTOCOpen; + } else if (this._attributes?.uiStateJSON) { + const uiState = JSON.parse(this._attributes.uiStateJSON); + if ('isLayerTOCOpen' in uiState) { + isLayerTOCOpen = uiState.isLayerTOCOpen; + } + } + this._store.dispatch(setIsLayerTOCOpen(isLayerTOCOpen)); + + let openTOCDetails = []; + if (this._mapEmbeddableInput && this._mapEmbeddableInput.openTOCDetails !== undefined) { + openTOCDetails = this._mapEmbeddableInput.openTOCDetails; + } else if (this._attributes?.uiStateJSON) { + const uiState = JSON.parse(this._attributes.uiStateJSON); + if ('openTOCDetails' in uiState) { + openTOCDetails = uiState.openTOCDetails; + } + } + this._store.dispatch(setOpenTOCDetails(openTOCDetails)); + + if (this._mapEmbeddableInput && this._mapEmbeddableInput.mapCenter !== undefined) { + this._store.dispatch( + setGotoWithCenter({ + lat: this._mapEmbeddableInput.mapCenter.lat, + lon: this._mapEmbeddableInput.mapCenter.lon, + zoom: this._mapEmbeddableInput.mapCenter.zoom, + }) + ); + } else if (this._attributes?.mapStateJSON) { + const mapState = JSON.parse(this._attributes.mapStateJSON); + this._store.dispatch( + setGotoWithCenter({ + lat: mapState.center.lat, + lon: mapState.center.lon, + zoom: mapState.zoom, + }) + ); + } + + const layerList = getInitialLayers(this._attributes.layerListJSON, this._defaultLayers); + this._store.dispatch(replaceLayerList(layerList)); + if (this._mapEmbeddableInput && this._mapEmbeddableInput.hiddenLayers !== undefined) { + this._store.dispatch(setHiddenLayers(this._mapEmbeddableInput.hiddenLayers)); + } + this._initialLayerListConfig = copyPersistentState(layerList); + } + + hasUnsavedChanges = () => { + if (!this._attributes) { + throw new Error('Invalid usage, must await whenReady before calling hasUnsavedChanges'); + } + + const savedLayerList = this._attributes.layerListJSON + ? JSON.parse(this._attributes.layerListJSON) + : null; + const layerListConfigOnly = getLayerListConfigOnly(this._store.getState()); + return !savedLayerList + ? !_.isEqual(layerListConfigOnly, this._initialLayerListConfig) + : // savedMap stores layerList as a JSON string using JSON.stringify. + // JSON.stringify removes undefined properties from objects. + // savedMap.getLayerList converts the JSON string back into Javascript array of objects. + // Need to perform the same process for layerListConfigOnly to compare apples to apples + // and avoid undefined properties in layerListConfigOnly triggering unsaved changes. + !_.isEqual(JSON.parse(JSON.stringify(layerListConfigOnly)), savedLayerList); + }; + + private _getStateTransfer() { + if (!this._stateTransfer) { + throw new Error('stateTransfer not provided in constructor'); + } + + return this._stateTransfer; + } + + private _getPageTitle(): string { + if (!this._mapEmbeddableInput) { + return i18n.translate('xpack.maps.breadcrumbsCreate', { + defaultMessage: 'Create', + }); + } + + return this.isByValue() + ? i18n.translate('xpack.maps.breadcrumbsEditByValue', { + defaultMessage: 'Edit map', + }) + : this._attributes!.title; + } + + setBreadcrumbs() { + if (!this._attributes) { + throw new Error('Invalid usage, must await whenReady before calling hasUnsavedChanges'); + } + + const breadcrumbs = getBreadcrumbs({ + pageTitle: this._getPageTitle(), + isByValue: this.isByValue(), + getHasUnsavedChanges: this.hasUnsavedChanges, + originatingApp: this._originatingApp, + getAppNameFromId: this._getStateTransfer().getAppNameFromId, + }); + getCoreChrome().setBreadcrumbs(breadcrumbs); + } + + public getSavedObjectId(): string | undefined { + return this._mapEmbeddableInput && 'savedObjectId' in this._mapEmbeddableInput + ? (this._mapEmbeddableInput as MapByReferenceInput).savedObjectId + : undefined; + } + + public getOriginatingApp(): string | undefined { + return this._originatingApp; + } + + public getAppNameFromId = (appId: string): string | undefined => { + return this._getStateTransfer().getAppNameFromId(appId); + }; + + public hasSaveAndReturnConfig() { + const hasOriginatingApp = !!this._originatingApp; + const isNewMap = !this.getSavedObjectId(); + return getIsAllowByValueEmbeddables() ? hasOriginatingApp : !isNewMap && hasOriginatingApp; + } + + public getTitle(): string { + if (!this._attributes) { + throw new Error('Invalid usage, must await getTitle before calling getAttributes'); + } + return this._attributes.title !== undefined ? this._attributes.title : ''; + } + + public getAttributes(): MapSavedObjectAttributes { + if (!this._attributes) { + throw new Error('Invalid usage, must await whenReady before calling getAttributes'); + } + + return this._attributes; + } + + public isByValue(): boolean { + const hasSavedObjectId = !!this.getSavedObjectId(); + return getIsAllowByValueEmbeddables() && !!this._originatingApp && !hasSavedObjectId; + } + + public async save({ + newDescription, + newTitle, + newCopyOnSave, + returnToOrigin, + saveByReference, + }: OnSaveProps & { + returnToOrigin: boolean; + saveByReference: boolean; + }) { + if (!this._attributes) { + throw new Error('Invalid usage, must await whenReady before calling save'); + } + + const prevTitle = this._attributes.title; + const prevDescription = this._attributes.description; + this._attributes.title = newTitle; + this._attributes.description = newDescription; + this._syncAttributesWithStore(); + + let updatedMapEmbeddableInput: MapEmbeddableInput; + try { + updatedMapEmbeddableInput = (await getMapAttributeService().wrapAttributes( + this._attributes, + saveByReference, + newCopyOnSave ? undefined : this._mapEmbeddableInput + )) as MapEmbeddableInput; + } catch (e) { + // Error toast displayed by wrapAttributes + this._attributes.title = prevTitle; + this._attributes.description = prevDescription; + return; + } + + if (returnToOrigin) { + if (!this._originatingApp) { + getToasts().addDanger({ + title: i18n.translate('xpack.maps.topNav.saveErrorTitle', { + defaultMessage: `Error saving '{title}'`, + values: { title: newTitle }, + }), + text: i18n.translate('xpack.maps.topNav.saveErrorText', { + defaultMessage: 'Unable to return to app without an originating app', + }), + }); + return; + } + this._getStateTransfer().navigateToWithEmbeddablePackage(this._originatingApp, { + state: { + embeddableId: newCopyOnSave ? undefined : this._embeddableId, + type: MAP_SAVED_OBJECT_TYPE, + input: updatedMapEmbeddableInput, + }, + }); + return; + } + + this._mapEmbeddableInput = updatedMapEmbeddableInput; + // break connection to originating application + this._originatingApp = undefined; + getToasts().addSuccess({ + title: i18n.translate('xpack.maps.topNav.saveSuccessMessage', { + defaultMessage: `Saved '{title}'`, + values: { title: newTitle }, + }), + }); + + getCoreChrome().docTitle.change(newTitle); + this.setBreadcrumbs(); + goToSpecifiedPath(`/${MAP_PATH}/${this.getSavedObjectId()}${window.location.hash}`); + + if (this._onSaveCallback) { + this._onSaveCallback(); + } + + return; + } + + private _syncAttributesWithStore() { + const state: MapStoreState = this._store.getState(); + const layerList = getLayerListRaw(state); + const layerListConfigOnly = copyPersistentState(layerList); + this._attributes!.layerListJSON = JSON.stringify(layerListConfigOnly); + + this._attributes!.mapStateJSON = JSON.stringify({ + zoom: getMapZoom(state), + center: getMapCenter(state), + timeFilters: getTimeFilters(state), + refreshConfig: getRefreshConfig(state), + query: _.omit(getQuery(state), 'queryLastTriggeredAt'), + filters: getFilters(state), + settings: getMapSettings(state), + }); + + this._attributes!.uiStateJSON = JSON.stringify({ + isLayerTOCOpen: getIsLayerTOCOpen(state), + openTOCDetails: getOpenTOCDetails(state), + }); + } +} 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 new file mode 100644 index 0000000000000..2d0a7d967a6cf --- /dev/null +++ b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx @@ -0,0 +1,202 @@ +/* + * 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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { Adapters } from 'src/plugins/inspector/public'; +import { + getCoreChrome, + getMapsCapabilities, + getInspector, + getCoreI18n, + getSavedObjectsClient, + getCoreOverlays, +} from '../../kibana_services'; +import { + checkForDuplicateTitle, + SavedObjectSaveModalOrigin, + OnSaveProps, + showSaveModal, +} from '../../../../../../src/plugins/saved_objects/public'; +import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; +import { SavedMap } from './saved_map'; +import { getMapEmbeddableDisplayName } from '../../../common/i18n_getters'; + +export function getTopNavConfig({ + savedMap, + isOpenSettingsDisabled, + isSaveDisabled, + enableFullScreen, + openMapSettings, + inspectorAdapters, +}: { + savedMap: SavedMap; + isOpenSettingsDisabled: boolean; + isSaveDisabled: boolean; + enableFullScreen: () => void; + openMapSettings: () => void; + inspectorAdapters: Adapters; +}) { + const topNavConfigs = []; + + topNavConfigs.push( + { + id: 'mapSettings', + label: i18n.translate('xpack.maps.topNav.openSettingsButtonLabel', { + defaultMessage: `Map settings`, + }), + description: i18n.translate('xpack.maps.topNav.openSettingsDescription', { + defaultMessage: `Open map settings`, + }), + testId: 'openSettingsButton', + disableButton() { + return isOpenSettingsDisabled; + }, + run() { + openMapSettings(); + }, + }, + { + id: 'inspect', + label: i18n.translate('xpack.maps.topNav.openInspectorButtonLabel', { + defaultMessage: `inspect`, + }), + description: i18n.translate('xpack.maps.topNav.openInspectorDescription', { + defaultMessage: `Open Inspector`, + }), + testId: 'openInspectorButton', + run() { + getInspector().open(inspectorAdapters, {}); + }, + }, + { + id: 'full-screen', + label: i18n.translate('xpack.maps.topNav.fullScreenButtonLabel', { + defaultMessage: `full screen`, + }), + description: i18n.translate('xpack.maps.topNav.fullScreenDescription', { + defaultMessage: `full screen`, + }), + testId: 'mapsFullScreenMode', + run() { + getCoreChrome().setIsVisible(false); + enableFullScreen(); + }, + } + ); + + if (getMapsCapabilities().save) { + const hasSaveAndReturnConfig = savedMap.hasSaveAndReturnConfig(); + const mapDescription = savedMap.getAttributes().description + ? savedMap.getAttributes().description! + : ''; + const saveAndReturnButtonLabel = savedMap.isByValue() + ? i18n.translate('xpack.maps.topNav.saveToMapsButtonLabel', { + defaultMessage: 'Save to maps', + }) + : i18n.translate('xpack.maps.topNav.saveAsButtonLabel', { + defaultMessage: 'Save as', + }); + + topNavConfigs.push({ + id: 'save', + iconType: hasSaveAndReturnConfig ? undefined : 'save', + label: hasSaveAndReturnConfig + ? saveAndReturnButtonLabel + : i18n.translate('xpack.maps.topNav.saveMapButtonLabel', { + defaultMessage: `save`, + }), + description: i18n.translate('xpack.maps.topNav.saveMapDescription', { + defaultMessage: `Save map`, + }), + emphasize: !hasSaveAndReturnConfig, + testId: 'mapSaveButton', + disableButton() { + return isSaveDisabled; + }, + tooltip() { + if (isSaveDisabled) { + return i18n.translate('xpack.maps.topNav.saveMapDisabledButtonTooltip', { + defaultMessage: 'Confirm or Cancel your layer changes before saving', + }); + } + }, + run: () => { + 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 {}; + } + + await savedMap.save({ + ...props, + 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', + })} + /> + ); + showSaveModal(saveModal, getCoreI18n().Context); + }, + }); + + if (hasSaveAndReturnConfig) { + topNavConfigs.push({ + id: 'saveAndReturn', + label: i18n.translate('xpack.maps.topNav.saveAndReturnButtonLabel', { + defaultMessage: 'Save and return', + }), + emphasize: true, + iconType: 'checkInCircleFilled', + run: () => { + savedMap.save({ + newTitle: savedMap.getTitle(), + newDescription: mapDescription, + newCopyOnSave: false, + isTitleDuplicateConfirmed: false, + returnToOrigin: true, + onTitleDuplicate: () => {}, + saveByReference: !savedMap.isByValue(), + }); + }, + testId: 'mapSaveAndReturnButton', + }); + } + } + + return topNavConfigs; +} diff --git a/x-pack/plugins/maps/public/routing/state_syncing/app_state_manager.ts b/x-pack/plugins/maps/public/routes/map_page/url_state/app_state_manager.ts similarity index 100% rename from x-pack/plugins/maps/public/routing/state_syncing/app_state_manager.ts rename to x-pack/plugins/maps/public/routes/map_page/url_state/app_state_manager.ts diff --git a/x-pack/plugins/maps/public/routing/state_syncing/app_sync.ts b/x-pack/plugins/maps/public/routes/map_page/url_state/app_sync.ts similarity index 90% rename from x-pack/plugins/maps/public/routing/state_syncing/app_sync.ts rename to x-pack/plugins/maps/public/routes/map_page/url_state/app_sync.ts index 498442040681c..5c6274d5a619d 100644 --- a/x-pack/plugins/maps/public/routing/state_syncing/app_sync.ts +++ b/x-pack/plugins/maps/public/routes/map_page/url_state/app_sync.ts @@ -5,10 +5,13 @@ */ import { map } from 'rxjs/operators'; -import { connectToQueryState, esFilters } from '../../../../../../src/plugins/data/public'; -import { syncState, BaseStateContainer } from '../../../../../../src/plugins/kibana_utils/public'; -import { getData } from '../../kibana_services'; -import { kbnUrlStateStorage } from '../render_app'; +import { connectToQueryState, esFilters } from '../../../../../../../src/plugins/data/public'; +import { + syncState, + BaseStateContainer, +} from '../../../../../../../src/plugins/kibana_utils/public'; +import { getData } from '../../../kibana_services'; +import { kbnUrlStateStorage } from '../../../render_app'; import { AppStateManager } from './app_state_manager'; export function startAppStateSyncing(appStateManager: AppStateManager) { diff --git a/x-pack/plugins/maps/public/routing/state_syncing/global_sync.ts b/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts similarity index 83% rename from x-pack/plugins/maps/public/routing/state_syncing/global_sync.ts rename to x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts index 3f370d9aa99b2..7fefc6662ada7 100644 --- a/x-pack/plugins/maps/public/routing/state_syncing/global_sync.ts +++ b/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { TimeRange, RefreshInterval, Filter } from 'src/plugins/data/public'; -import { syncQueryStateWithUrl } from '../../../../../../src/plugins/data/public'; -import { getData } from '../../kibana_services'; -import { kbnUrlStateStorage } from '../render_app'; +import { syncQueryStateWithUrl } from '../../../../../../../src/plugins/data/public'; +import { getData } from '../../../kibana_services'; +import { kbnUrlStateStorage } from '../../../render_app'; export interface MapsGlobalState { time?: TimeRange; diff --git a/x-pack/plugins/maps/public/routes/map_page/url_state/index.ts b/x-pack/plugins/maps/public/routes/map_page/url_state/index.ts new file mode 100644 index 0000000000000..3072ab8280c41 --- /dev/null +++ b/x-pack/plugins/maps/public/routes/map_page/url_state/index.ts @@ -0,0 +1,14 @@ +/* + * 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. + */ + +export { + getGlobalState, + updateGlobalState, + startGlobalStateSyncing, + MapsGlobalState, +} from './global_sync'; +export { AppStateManager, MapsAppState } from './app_state_manager'; +export { startAppStateSyncing } from './app_sync'; diff --git a/x-pack/plugins/maps/public/routing/bootstrap/services/gis_map_saved_object_loader.ts b/x-pack/plugins/maps/public/routing/bootstrap/services/gis_map_saved_object_loader.ts deleted file mode 100644 index fe8aa02615b85..0000000000000 --- a/x-pack/plugins/maps/public/routing/bootstrap/services/gis_map_saved_object_loader.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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 _ from 'lodash'; -import { createSavedGisMapClass } from './saved_gis_map'; -import { SavedObjectLoader } from '../../../../../../../src/plugins/saved_objects/public'; -import { getSavedObjects, getSavedObjectsClient } from '../../../kibana_services'; - -export const getMapsSavedObjectLoader = _.once(function () { - const SavedGisMap = createSavedGisMapClass(getSavedObjects()); - - return new SavedObjectLoader(SavedGisMap, getSavedObjectsClient()); -}); diff --git a/x-pack/plugins/maps/public/routing/bootstrap/services/saved_gis_map.ts b/x-pack/plugins/maps/public/routing/bootstrap/services/saved_gis_map.ts deleted file mode 100644 index 7b31d9edea90d..0000000000000 --- a/x-pack/plugins/maps/public/routing/bootstrap/services/saved_gis_map.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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 _ from 'lodash'; -import { SavedObjectReference } from 'kibana/public'; -import { i18n } from '@kbn/i18n'; -import { - SavedObjectsStart, - SavedObject, -} from '../../../../../../../src/plugins/saved_objects/public'; -import { - getTimeFilters, - getMapZoom, - getMapCenter, - getLayerListRaw, - getRefreshConfig, - getQuery, - getFilters, - getMapSettings, -} from '../../../selectors/map_selectors'; -import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../../selectors/ui_selectors'; -import { copyPersistentState } from '../../../reducers/util'; -// @ts-expect-error -import { extractReferences, injectReferences } from '../../../../common/migrations/references'; -import { getExistingMapPath, MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants'; -import { getStore } from '../../store_operations'; -import { MapStoreState } from '../../../reducers/store'; -import { LayerDescriptor } from '../../../../common/descriptor_types'; - -export interface ISavedGisMap extends SavedObject { - layerListJSON?: string; - mapStateJSON?: string; - uiStateJSON?: string; - description?: string; - getLayerList(): LayerDescriptor[]; - syncWithStore(): void; -} - -export function createSavedGisMapClass(savedObjects: SavedObjectsStart) { - class SavedGisMap extends savedObjects.SavedObjectClass implements ISavedGisMap { - public static type = MAP_SAVED_OBJECT_TYPE; - - // Mappings are used to place object properties into saved object _source - public static mapping = { - title: 'text', - description: 'text', - mapStateJSON: 'text', - layerListJSON: 'text', - uiStateJSON: 'text', - }; - public static fieldOrder = ['title', 'description']; - public static searchSource = false; - - public showInRecentlyAccessed = true; - public layerListJSON?: string; - public mapStateJSON?: string; - public uiStateJSON?: string; - - constructor(id: string) { - super({ - type: SavedGisMap.type, - mapping: SavedGisMap.mapping, - searchSource: SavedGisMap.searchSource, - extractReferences, - injectReferences: (savedObject: ISavedGisMap, references: SavedObjectReference[]) => { - const { attributes } = injectReferences({ - attributes: { layerListJSON: savedObject.layerListJSON }, - references, - }); - - savedObject.layerListJSON = attributes.layerListJSON; - }, - - // if this is null/undefined then the SavedObject will be assigned the defaults - id, - - // default values that will get assigned if the doc is new - defaults: { - title: i18n.translate('xpack.maps.newMapTitle', { - defaultMessage: 'New Map', - }), - description: '', - }, - }); - - this.getFullPath = () => { - return getExistingMapPath(this.id!); - }; - } - - getLayerList() { - return this.layerListJSON ? JSON.parse(this.layerListJSON) : null; - } - - syncWithStore() { - const state: MapStoreState = getStore().getState(); - const layerList = getLayerListRaw(state); - const layerListConfigOnly = copyPersistentState(layerList); - this.layerListJSON = JSON.stringify(layerListConfigOnly); - - this.mapStateJSON = JSON.stringify({ - zoom: getMapZoom(state), - center: getMapCenter(state), - timeFilters: getTimeFilters(state), - refreshConfig: getRefreshConfig(state), - query: _.omit(getQuery(state), 'queryLastTriggeredAt'), - filters: getFilters(state), - settings: getMapSettings(state), - }); - - this.uiStateJSON = JSON.stringify({ - isLayerTOCOpen: getIsLayerTOCOpen(state), - openTOCDetails: getOpenTOCDetails(state), - }); - } - } - return SavedGisMap; -} diff --git a/x-pack/plugins/maps/public/routing/render_app.tsx b/x-pack/plugins/maps/public/routing/render_app.tsx deleted file mode 100644 index 65cccc53f5047..0000000000000 --- a/x-pack/plugins/maps/public/routing/render_app.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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 React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { Router, Switch, Route, Redirect } from 'react-router-dom'; -import { i18n } from '@kbn/i18n'; -import { Provider } from 'react-redux'; -import { AppMountParameters } from 'kibana/public'; -import { - getCoreChrome, - getCoreI18n, - getMapsCapabilities, - getToasts, - getEmbeddableService, -} from '../kibana_services'; -import { - createKbnUrlStateStorage, - withNotifyOnErrors, - IKbnUrlStateStorage, -} from '../../../../../src/plugins/kibana_utils/public'; -import { getStore } from './store_operations'; -import { LoadListAndRender } from './routes/list/load_list_and_render'; -import { MapApp } from './routes/map_app'; - -export let goToSpecifiedPath: (path: string) => void; -export let kbnUrlStateStorage: IKbnUrlStateStorage; - -export async function renderApp({ - element, - history, - onAppLeave, - setHeaderActionMenu, -}: AppMountParameters) { - goToSpecifiedPath = (path) => history.push(path); - kbnUrlStateStorage = createKbnUrlStateStorage({ - useHash: false, - history, - ...withNotifyOnErrors(getToasts()), - }); - - const store = getStore(); - const I18nContext = getCoreI18n().Context; - - const stateTransfer = getEmbeddableService()?.getStateTransfer( - history as AppMountParameters['history'] - ); - - const { originatingApp } = - stateTransfer?.getIncomingEditorState({ keysToRemoveAfterFetch: ['originatingApp'] }) || {}; - - if (!getMapsCapabilities().save) { - getCoreChrome().setBadge({ - text: i18n.translate('xpack.maps.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('xpack.maps.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save maps', - }), - iconType: 'glasses', - }); - } - - render( - - - - - ( - - )} - /> - ( - - )} - /> - // Redirect other routes to list, or if hash-containing, their non-hash equivalents - { - if (hash) { - // Remove leading hash - const newPath = hash.substr(1); - return ; - } else if (pathname === '/' || pathname === '') { - return ; - } else { - return ; - } - }} - /> - - - - , - element - ); - - return () => { - unmountComponentAtNode(element); - }; -} diff --git a/x-pack/plugins/maps/public/routing/routes/map_app/top_nav_config.tsx b/x-pack/plugins/maps/public/routing/routes/map_app/top_nav_config.tsx deleted file mode 100644 index c60f44093541f..0000000000000 --- a/x-pack/plugins/maps/public/routing/routes/map_app/top_nav_config.tsx +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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 React from 'react'; -import { i18n } from '@kbn/i18n'; -import { Adapters } from 'src/plugins/inspector/public'; -import { - getCoreChrome, - getMapsCapabilities, - getInspector, - getToasts, - getCoreI18n, - getNavigateToApp, -} from '../../../kibana_services'; -import { - SavedObjectSaveModalOrigin, - OnSaveProps, - showSaveModal, -} from '../../../../../../../src/plugins/saved_objects/public'; -import { MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants'; -import { goToSpecifiedPath } from '../../render_app'; -import { ISavedGisMap } from '../../bootstrap/services/saved_gis_map'; -import { EmbeddableStateTransfer } from '../../../../../../../src/plugins/embeddable/public'; - -export function getTopNavConfig({ - savedMap, - isOpenSettingsDisabled, - isSaveDisabled, - enableFullScreen, - openMapSettings, - inspectorAdapters, - setBreadcrumbs, - stateTransfer, - originatingApp, - cutOriginatingAppConnection, -}: { - savedMap: ISavedGisMap; - isOpenSettingsDisabled: boolean; - isSaveDisabled: boolean; - enableFullScreen: () => void; - openMapSettings: () => void; - inspectorAdapters: Adapters; - setBreadcrumbs: (title: string) => void; - stateTransfer?: EmbeddableStateTransfer; - originatingApp?: string; - cutOriginatingAppConnection: () => void; -}) { - const topNavConfigs = []; - const isNewMap = !savedMap.id; - const hasWritePermissions = getMapsCapabilities().save; - const hasSaveAndReturnConfig = hasWritePermissions && !isNewMap && originatingApp; - - async function onSave({ - newDescription, - newTitle, - newCopyOnSave, - isTitleDuplicateConfirmed, - onTitleDuplicate, - returnToOrigin, - }: OnSaveProps & { returnToOrigin: boolean }) { - const prevTitle = savedMap.title; - const prevDescription = savedMap.description; - savedMap.title = newTitle; - savedMap.description = newDescription; - savedMap.copyOnSave = newCopyOnSave; - - let savedObjectId; - try { - savedMap.syncWithStore(); - savedObjectId = await savedMap.save({ - confirmOverwrite: false, - isTitleDuplicateConfirmed, - onTitleDuplicate, - }); - // id not returned when save fails because of duplicate title check. - // return and let user confirm duplicate title. - if (!savedObjectId) { - return {}; - } - } catch (err) { - getToasts().addDanger({ - title: i18n.translate('xpack.maps.topNav.saveErrorMessage', { - defaultMessage: `Error saving '{title}'`, - values: { title: savedMap.title }, - }), - text: err.message, - 'data-test-subj': 'saveMapError', - }); - // If the save wasn't successful, put the original values back. - savedMap.title = prevTitle; - savedMap.description = prevDescription; - return { error: err }; - } - - getToasts().addSuccess({ - title: i18n.translate('xpack.maps.topNav.saveSuccessMessage', { - defaultMessage: `Saved '{title}'`, - values: { title: savedMap.title }, - }), - 'data-test-subj': 'saveMapSuccess', - }); - - getCoreChrome().docTitle.change(savedMap.title); - setBreadcrumbs(savedMap.title); - goToSpecifiedPath(`/map/${savedObjectId}${window.location.hash}`); - - const newlyCreated = newCopyOnSave || isNewMap; - if (newlyCreated && !returnToOrigin) { - cutOriginatingAppConnection(); - } else if (!!originatingApp && returnToOrigin) { - if (newlyCreated && stateTransfer) { - stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { - state: { input: { savedObjectId }, type: MAP_SAVED_OBJECT_TYPE }, - }); - } else { - getNavigateToApp()(originatingApp); - } - } - - return { id: savedObjectId }; - } - - topNavConfigs.push( - { - id: 'mapSettings', - label: i18n.translate('xpack.maps.topNav.openSettingsButtonLabel', { - defaultMessage: `Map settings`, - }), - description: i18n.translate('xpack.maps.topNav.openSettingsDescription', { - defaultMessage: `Open map settings`, - }), - testId: 'openSettingsButton', - disableButton() { - return isOpenSettingsDisabled; - }, - run() { - openMapSettings(); - }, - }, - { - id: 'inspect', - label: i18n.translate('xpack.maps.topNav.openInspectorButtonLabel', { - defaultMessage: `inspect`, - }), - description: i18n.translate('xpack.maps.topNav.openInspectorDescription', { - defaultMessage: `Open Inspector`, - }), - testId: 'openInspectorButton', - run() { - getInspector().open(inspectorAdapters, {}); - }, - }, - { - id: 'full-screen', - label: i18n.translate('xpack.maps.topNav.fullScreenButtonLabel', { - defaultMessage: `full screen`, - }), - description: i18n.translate('xpack.maps.topNav.fullScreenDescription', { - defaultMessage: `full screen`, - }), - testId: 'mapsFullScreenMode', - run() { - getCoreChrome().setIsVisible(false); - enableFullScreen(); - }, - } - ); - - if (hasWritePermissions) { - topNavConfigs.push({ - id: 'save', - iconType: hasSaveAndReturnConfig ? undefined : 'save', - label: hasSaveAndReturnConfig - ? i18n.translate('xpack.maps.topNav.saveAsButtonLabel', { - defaultMessage: 'Save as', - }) - : i18n.translate('xpack.maps.topNav.saveMapButtonLabel', { - defaultMessage: `save`, - }), - description: i18n.translate('xpack.maps.topNav.saveMapDescription', { - defaultMessage: `Save map`, - }), - emphasize: !hasSaveAndReturnConfig, - testId: 'mapSaveButton', - disableButton() { - return isSaveDisabled; - }, - tooltip() { - if (isSaveDisabled) { - return i18n.translate('xpack.maps.topNav.saveMapDisabledButtonTooltip', { - defaultMessage: 'Confirm or Cancel your layer changes before saving', - }); - } - }, - run: () => { - const saveModal = ( - {}} - documentInfo={{ - description: savedMap.description, - id: savedMap.id, - title: savedMap.title, - }} - objectType={i18n.translate('xpack.maps.topNav.saveModalType', { - defaultMessage: 'map', - })} - /> - ); - showSaveModal(saveModal, getCoreI18n().Context); - }, - }); - } - - if (hasSaveAndReturnConfig) { - topNavConfigs.push({ - id: 'saveAndReturn', - label: i18n.translate('xpack.maps.topNav.saveAndReturnButtonLabel', { - defaultMessage: 'Save and return', - }), - emphasize: true, - iconType: 'checkInCircleFilled', - run: () => { - onSave({ - newTitle: savedMap.title ? savedMap.title : '', - newDescription: savedMap.description ? savedMap.description : '', - newCopyOnSave: false, - isTitleDuplicateConfirmed: false, - returnToOrigin: true, - onTitleDuplicate: () => {}, - }); - }, - testId: 'mapSaveAndReturnButton', - }); - } - - return topNavConfigs; -} diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts index 7f50ca9045038..eaac4ac3a17b7 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts @@ -24,6 +24,9 @@ jest.mock('../kibana_services', () => ({ }; }, }), + getMapsCapabilities() { + return { save: true }; + }, })); import { DEFAULT_MAP_STORE_STATE } from '../reducers/store'; diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index eac71e627fd7d..f6282be26b40c 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -372,13 +372,25 @@ export const getUniqueIndexPatternIds = createSelector(getLayerList, (layerList) }); // Get list of unique index patterns, excluding index patterns from layers that disable applyGlobalQuery -export const getQueryableUniqueIndexPatternIds = createSelector(getLayerList, (layerList) => { - const indexPatternIds: string[] = []; - layerList.forEach((layer) => { - indexPatternIds.push(...layer.getQueryableIndexPatternIds()); - }); - return _.uniq(indexPatternIds); -}); +export const getQueryableUniqueIndexPatternIds = createSelector( + getLayerList, + getWaitingForMapReadyLayerListRaw, + (layerList, waitingForMapReadyLayerList) => { + const indexPatternIds: string[] = []; + + if (waitingForMapReadyLayerList.length) { + waitingForMapReadyLayerList.forEach((layerDescriptor) => { + const layer = createLayerInstance(layerDescriptor); + indexPatternIds.push(...layer.getQueryableIndexPatternIds()); + }); + } else { + layerList.forEach((layer) => { + indexPatternIds.push(...layer.getQueryableIndexPatternIds()); + }); + } + return _.uniq(indexPatternIds); + } +); export const hasDirtyState = createSelector(getLayerListRaw, (layerListRaw) => { return layerListRaw.some((layerDescriptor) => { diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx index 4ac759ea534ec..1d6ace3292a0d 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx @@ -68,6 +68,7 @@ export const createEmbeddable = async ( const input: MapEmbeddableInput = { title: i18n.MAP_TITLE, + attributes: { title: '' }, id: uuid.v4(), filters, hidePanelTitles: true, @@ -115,7 +116,7 @@ export const createEmbeddable = async ( if (!isErrorEmbeddable(embeddableObject)) { embeddableObject.setRenderTooltipContent(renderTooltipContent); // @ts-expect-error - await embeddableObject.setLayerList(getLayerList(indexPatterns)); + embeddableObject.setLayerList(getLayerList(indexPatterns)); } // Wire up to app refresh action diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d9a113b69b473..3f0db7fe5a99c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -11293,8 +11293,6 @@ "xpack.maps.layerWizardSelect.solutionsCategoryLabel": "ソリューション", "xpack.maps.loadMap.errorAttemptingToLoadSavedMap": "マップを読み込めません", "xpack.maps.map.initializeErrorTitle": "マップを初期化できません", - "xpack.maps.mapEmbeddableFactory.invalidLayerList": "不正な形式のレイヤーリストによりマップを読み込めません", - "xpack.maps.mapEmbeddableFactory.invalidSavedObject": "不正な形式の保存済みオブジェクトによりマップを読み込めません", "xpack.maps.mapListing.advancedSettingsLinkText": "高度な設定", "xpack.maps.mapListing.cancelTitle": "キャンセル", "xpack.maps.mapListing.createMapButtonLabel": "マップを作成", @@ -11358,7 +11356,6 @@ "xpack.maps.mvtSource.tooltipsTitle": "ツールチップフィールド", "xpack.maps.mvtSource.trashButtonAriaLabel": "フィールドの削除", "xpack.maps.mvtSource.trashButtonTitle": "フィールドの削除", - "xpack.maps.newMapTitle": "新しいマップ", "xpack.maps.noIndexPattern.doThisLinkTextDescription": "インデックスパターンを作成します", "xpack.maps.noIndexPattern.doThisPrefixDescription": "次のことが必要です ", "xpack.maps.noIndexPattern.doThisSuffixDescription": " 地理空間フィールドを含む", @@ -11612,7 +11609,6 @@ "xpack.maps.topNav.openSettingsDescription": "マップ設定を開く", "xpack.maps.topNav.saveAndReturnButtonLabel": "保存して戻る", "xpack.maps.topNav.saveAsButtonLabel": "名前を付けて保存", - "xpack.maps.topNav.saveErrorMessage": "「{title}」の保存エラー", "xpack.maps.topNav.saveMapButtonLabel": "保存", "xpack.maps.topNav.saveMapDescription": "マップを保存", "xpack.maps.topNav.saveMapDisabledButtonTooltip": "保存する前に、レイヤーの変更を保存するか、キャンセルしてください", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4a393f8a7a223..e46cfb6658ea6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11306,8 +11306,6 @@ "xpack.maps.layerWizardSelect.solutionsCategoryLabel": "解决方案", "xpack.maps.loadMap.errorAttemptingToLoadSavedMap": "无法加载地图", "xpack.maps.map.initializeErrorTitle": "无法初始化地图", - "xpack.maps.mapEmbeddableFactory.invalidLayerList": "无法加载地图,图层列表格式不正确", - "xpack.maps.mapEmbeddableFactory.invalidSavedObject": "无法加载地图,已保存对象格式错误", "xpack.maps.mapListing.advancedSettingsLinkText": "高级设置", "xpack.maps.mapListing.cancelTitle": "取消", "xpack.maps.mapListing.createMapButtonLabel": "创建地图", @@ -11371,7 +11369,6 @@ "xpack.maps.mvtSource.tooltipsTitle": "工具提示字段", "xpack.maps.mvtSource.trashButtonAriaLabel": "移除字段", "xpack.maps.mvtSource.trashButtonTitle": "移除字段", - "xpack.maps.newMapTitle": "新地图", "xpack.maps.noIndexPattern.doThisLinkTextDescription": "创建索引模式", "xpack.maps.noIndexPattern.doThisPrefixDescription": "您将需要 ", "xpack.maps.noIndexPattern.doThisSuffixDescription": " 使用地理空间字段。", @@ -11625,7 +11622,6 @@ "xpack.maps.topNav.openSettingsDescription": "打开地图设置", "xpack.maps.topNav.saveAndReturnButtonLabel": "保存并返回", "xpack.maps.topNav.saveAsButtonLabel": "另存为", - "xpack.maps.topNav.saveErrorMessage": "保存“{title}”时出错", "xpack.maps.topNav.saveMapButtonLabel": "保存", "xpack.maps.topNav.saveMapDescription": "保存地图", "xpack.maps.topNav.saveMapDisabledButtonTooltip": "保存前确认或取消您的图层更改", diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/embedded_map.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/embedded_map.tsx index 85ac016333363..2da57ef3138c7 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/embedded_map.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/embedded_map.tsx @@ -66,6 +66,7 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp const input: MapEmbeddableInput = { id: uuid.v4(), + attributes: { title: '' }, filters: [], hidePanelTitles: true, refreshConfig: { @@ -127,7 +128,7 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp if (embeddableObject && !isErrorEmbeddable(embeddableObject)) { embeddableObject.setRenderTooltipContent(renderTooltipContent); - await embeddableObject.setLayerList(getLayerList(upPoints, downPoints, colors)); + embeddableObject.setLayerList(getLayerList(upPoints, downPoints, colors)); } setEmbeddable(embeddableObject);