From a8ebc7f4686e359e9893e7e6fed291711c38dad4 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 23 Sep 2024 14:58:09 -0600 Subject: [PATCH] [maps embeddable] fix map layers disappear from map panel when using session storage to continue editing a dashboard (#193629) Closes https://github.com/elastic/kibana/issues/190468 and https://github.com/elastic/kibana/issues/193601 Dashboard unsaved changes for a panel are obtained from the embeddable comparator subject for each comparator. If comparator subject does not return a value, then this information is lost when returning to a dashboard with unsaved changes. This is why layers and filters where disappearing from a map when returning to a dashboard with unsaved changes. This PR resolves this issue by providing comparator subjects and setters for `savedObjectId`, `attributes`, and `mapSettings`. PR also resolves issue of passing props from `MapRenderer` component into map embeddable. Instead of passing props via state, props should be passed from `MapRenderer` parent api. --------- Co-authored-by: Elastic Machine --- x-pack/plugins/maps/public/api/start_api.ts | 2 +- .../region_map/region_map_visualization.tsx | 2 +- .../tile_map/tile_map_visualization.tsx | 2 +- .../plugins/maps/public/lens/passive_map.tsx | 2 +- x-pack/plugins/maps/public/plugin.ts | 2 +- .../react_embeddable/map_react_embeddable.tsx | 36 +++++++++---- .../{ => map_renderer}/map_renderer.tsx | 50 ++++++++++--------- .../{ => map_renderer}/map_renderer_lazy.tsx | 0 .../react_embeddable/map_renderer/types.ts | 21 ++++++++ .../maps/public/react_embeddable/types.ts | 7 --- 10 files changed, 79 insertions(+), 45 deletions(-) rename x-pack/plugins/maps/public/react_embeddable/{ => map_renderer}/map_renderer.tsx (66%) rename x-pack/plugins/maps/public/react_embeddable/{ => map_renderer}/map_renderer_lazy.tsx (100%) create mode 100644 x-pack/plugins/maps/public/react_embeddable/map_renderer/types.ts diff --git a/x-pack/plugins/maps/public/api/start_api.ts b/x-pack/plugins/maps/public/api/start_api.ts index fcf9dab69e40a..4047c88268e04 100644 --- a/x-pack/plugins/maps/public/api/start_api.ts +++ b/x-pack/plugins/maps/public/api/start_api.ts @@ -9,7 +9,7 @@ import type { LayerDescriptor } from '../../common/descriptor_types'; import type { CreateLayerDescriptorParams } from '../classes/sources/es_search_source'; import type { SampleValuesConfig, EMSTermJoinConfig } from '../ems_autosuggest'; import type { Props as PassiveMapProps } from '../lens/passive_map'; -import type { Props as MapProps } from '../react_embeddable/map_renderer'; +import type { Props as MapProps } from '../react_embeddable/map_renderer/map_renderer'; export interface MapsStartApi { createLayerDescriptors: { diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx index d8bc9a56f062a..09cf94a097767 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx @@ -11,7 +11,7 @@ import { first } from 'rxjs'; import type { Filter } from '@kbn/es-query'; import type { Query, TimeRange } from '@kbn/es-query'; import { RegionMapVisConfig } from './types'; -import { MapRenderer } from '../../react_embeddable/map_renderer'; +import { MapRenderer } from '../../react_embeddable/map_renderer/map_renderer'; import { createRegionMapLayerDescriptor } from '../../classes/layers/create_region_map_layer_descriptor'; interface Props { diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx index f1cc8d437e082..35960c0c0dead 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx @@ -11,7 +11,7 @@ import { first } from 'rxjs'; import type { Filter } from '@kbn/es-query'; import type { Query, TimeRange } from '@kbn/es-query'; import type { TileMapVisConfig } from './types'; -import { MapRenderer } from '../../react_embeddable/map_renderer'; +import { MapRenderer } from '../../react_embeddable/map_renderer/map_renderer'; import { createTileMapLayerDescriptor } from '../../classes/layers/create_tile_map_layer_descriptor'; interface Props { diff --git a/x-pack/plugins/maps/public/lens/passive_map.tsx b/x-pack/plugins/maps/public/lens/passive_map.tsx index 30a4b8013b8ab..1be544a8cb423 100644 --- a/x-pack/plugins/maps/public/lens/passive_map.tsx +++ b/x-pack/plugins/maps/public/lens/passive_map.tsx @@ -53,6 +53,7 @@ export function PassiveMap(props: Props) { type={MAP_SAVED_OBJECT_TYPE} getParentApi={() => ({ + hideFilterActions: true, getSerializedStateForChild: () => { const basemapLayerDescriptor = createBasemapLayerDescriptor(); const intialLayers = basemapLayerDescriptor ? [basemapLayerDescriptor] : []; @@ -66,7 +67,6 @@ export function PassiveMap(props: Props) { hidePanelTitles: true, viewMode: ViewMode.VIEW, isLayerTOCOpen: false, - hideFilterActions: true, mapSettings: { disableInteractive: false, hideToolbarOverlay: false, diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index cc4cc4bcc91e8..fbdb2ef67d2ed 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -88,7 +88,7 @@ import { VectorTileInspectorView } from './inspector/vector_tile_adapter/vector_ import { PassiveMapLazy, setupLensChoroplethChart } from './lens'; import { CONTENT_ID, LATEST_VERSION } from '../common/content_management'; import { setupMapEmbeddable } from './react_embeddable/setup_map_embeddable'; -import { MapRendererLazy } from './react_embeddable/map_renderer_lazy'; +import { MapRendererLazy } from './react_embeddable/map_renderer/map_renderer_lazy'; export interface MapsPluginSetupDependencies { cloud?: CloudSetup; diff --git a/x-pack/plugins/maps/public/react_embeddable/map_react_embeddable.tsx b/x-pack/plugins/maps/public/react_embeddable/map_react_embeddable.tsx index 28a14592a8c32..b20c05dba244b 100644 --- a/x-pack/plugins/maps/public/react_embeddable/map_react_embeddable.tsx +++ b/x-pack/plugins/maps/public/react_embeddable/map_react_embeddable.tsx @@ -40,6 +40,9 @@ import { initializeDataViews } from './initialize_data_views'; import { initializeFetch } from './initialize_fetch'; import { initializeEditApi } from './initialize_edit_api'; import { extractReferences } from '../../common/migrations/references'; +import { MapAttributes } from '../../common/content_management'; +import { MapSettings } from '../../common/descriptor_types'; +import { isMapRendererApi } from './map_renderer/types'; export function getControlledBy(id: string) { return `mapEmbeddablePanel${id}`; @@ -65,6 +68,10 @@ export const mapEmbeddableFactory: ReactEmbeddableFactory< }); await savedMap.whenReady(); + const attributes$ = new BehaviorSubject(state.attributes); + const mapSettings$ = new BehaviorSubject | undefined>(state.mapSettings); + const savedObjectId$ = new BehaviorSubject(state.savedObjectId); + // eslint bug, eslint thinks api is never reassigned even though it is // eslint-disable-next-line prefer-const let api: MapApi | undefined; @@ -171,14 +178,14 @@ export const mapEmbeddableFactory: ReactEmbeddableFactory< }), ...crossPanelActions.comparators, ...reduxSync.comparators, + attributes: [attributes$, (next: MapAttributes | undefined) => attributes$.next(next)], + mapSettings: [ + mapSettings$, + (next: Partial | undefined) => mapSettings$.next(next), + ], + savedObjectId: [savedObjectId$, (next: string | undefined) => savedObjectId$.next(next)], // readonly comparators - attributes: getUnchangingComparator(), mapBuffer: getUnchangingComparator(), - savedObjectId: getUnchangingComparator(), - mapSettings: getUnchangingComparator(), - hideFilterActions: getUnchangingComparator(), - isSharable: getUnchangingComparator(), - tooltipRenderer: getUnchangingComparator(), } ); @@ -229,17 +236,28 @@ export const mapEmbeddableFactory: ReactEmbeddableFactory< ); diff --git a/x-pack/plugins/maps/public/react_embeddable/map_renderer.tsx b/x-pack/plugins/maps/public/react_embeddable/map_renderer/map_renderer.tsx similarity index 66% rename from x-pack/plugins/maps/public/react_embeddable/map_renderer.tsx rename to x-pack/plugins/maps/public/react_embeddable/map_renderer/map_renderer.tsx index 7701c7ebbe11b..459c99f9d981c 100644 --- a/x-pack/plugins/maps/public/react_embeddable/map_renderer.tsx +++ b/x-pack/plugins/maps/public/react_embeddable/map_renderer/map_renderer.tsx @@ -9,11 +9,16 @@ import React, { useEffect, useRef } from 'react'; import type { Filter, Query, TimeRange } from '@kbn/es-query'; import { ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public'; import { useSearchApi } from '@kbn/presentation-publishing'; -import type { LayerDescriptor, MapCenterAndZoom, MapSettings } from '../../common/descriptor_types'; -import { createBasemapLayerDescriptor } from '../classes/layers/create_basemap_layer_descriptor'; -import { MapApi, MapRuntimeState, MapSerializedState } from './types'; -import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; -import { RenderToolTipContent } from '../classes/tooltips/tooltip_property'; +import type { + LayerDescriptor, + MapCenterAndZoom, + MapSettings, +} from '../../../common/descriptor_types'; +import { createBasemapLayerDescriptor } from '../../classes/layers/create_basemap_layer_descriptor'; +import { MapApi, MapRuntimeState, MapSerializedState } from '../types'; +import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; +import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property'; +import { MAP_RENDERER_TYPE } from './types'; function getLayers(layerList: LayerDescriptor[]) { const basemapLayer = createBasemapLayerDescriptor(); @@ -61,30 +66,27 @@ export function MapRenderer(props: Props) { type={MAP_SAVED_OBJECT_TYPE} getParentApi={() => ({ - ...searchApi, + type: MAP_RENDERER_TYPE, + getTooltipRenderer: props.getTooltipRenderer, + hideFilterActions: props.hideFilterActions, + isSharable: props.isSharable, getSerializedStateForChild: () => { - const rawState: MapSerializedState = { - attributes: { - title: props.title ?? '', - layerListJSON: JSON.stringify(getLayers(props.layerList)), - }, - hidePanelTitles: !Boolean(props.title), - isLayerTOCOpen: - typeof props.isLayerTOCOpen === 'boolean' ? props.isLayerTOCOpen : false, - hideFilterActions: - typeof props.hideFilterActions === 'boolean' ? props.hideFilterActions : false, - mapCenter: props.mapCenter, - mapSettings: props.mapSettings ?? {}, - isSharable: props.isSharable, - }; - if (props.getTooltipRenderer) { - rawState.tooltipRenderer = props.getTooltipRenderer(); - } return { - rawState, + rawState: { + attributes: { + title: props.title ?? '', + layerListJSON: JSON.stringify(getLayers(props.layerList)), + }, + hidePanelTitles: !Boolean(props.title), + isLayerTOCOpen: + typeof props.isLayerTOCOpen === 'boolean' ? props.isLayerTOCOpen : false, + mapCenter: props.mapCenter, + mapSettings: props.mapSettings ?? {}, + }, references: [], }; }, + ...searchApi, })} onApiAvailable={(api) => { mapApiRef.current = api; diff --git a/x-pack/plugins/maps/public/react_embeddable/map_renderer_lazy.tsx b/x-pack/plugins/maps/public/react_embeddable/map_renderer/map_renderer_lazy.tsx similarity index 100% rename from x-pack/plugins/maps/public/react_embeddable/map_renderer_lazy.tsx rename to x-pack/plugins/maps/public/react_embeddable/map_renderer/map_renderer_lazy.tsx diff --git a/x-pack/plugins/maps/public/react_embeddable/map_renderer/types.ts b/x-pack/plugins/maps/public/react_embeddable/map_renderer/types.ts new file mode 100644 index 0000000000000..0e92e551c821a --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/map_renderer/types.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HasType, apiIsOfType } from '@kbn/presentation-publishing'; +import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property'; + +export const MAP_RENDERER_TYPE = 'mapRenderer'; + +export type MapRendererApi = HasType & { + getTooltipRenderer?: () => RenderToolTipContent; + hideFilterActions?: boolean; + isSharable?: boolean; +}; + +export function isMapRendererApi(api: unknown): api is MapRendererApi { + return Boolean(api && apiIsOfType(api, MAP_RENDERER_TYPE)); +} diff --git a/x-pack/plugins/maps/public/react_embeddable/types.ts b/x-pack/plugins/maps/public/react_embeddable/types.ts index f020e75723c6c..a61a7e1ce6a03 100644 --- a/x-pack/plugins/maps/public/react_embeddable/types.ts +++ b/x-pack/plugins/maps/public/react_embeddable/types.ts @@ -31,7 +31,6 @@ import { MapSettings, } from '../../common/descriptor_types'; import { ILayer } from '../classes/layers/layer'; -import { RenderToolTipContent } from '../classes/tooltips/tooltip_property'; import { EventHandlers } from '../reducers/non_serializable_instances'; export type MapSerializedState = SerializedTitles & @@ -47,15 +46,9 @@ export type MapSerializedState = SerializedTitles & mapBuffer?: MapExtent; mapSettings?: Partial; hiddenLayers?: string[]; - hideFilterActions?: boolean; timeRange?: TimeRange; filterByMapExtent?: boolean; isMovementSynchronized?: boolean; - - // Configuration item that are never persisted - // Putting in state as a temporary work around - isSharable?: boolean; - tooltipRenderer?: RenderToolTipContent; }; export type MapRuntimeState = MapSerializedState;