diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_embedded_map.scss b/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_embedded_map.scss deleted file mode 100644 index a3682bfd7d96c..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_embedded_map.scss +++ /dev/null @@ -1,8 +0,0 @@ -.embeddedMap__content { - width: 100%; - height: 100%; - display: flex; - flex: 1 1 100%; - z-index: 1; - min-height: 0; // Absolute must for Firefox to scroll contents -} diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_index.scss b/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_index.scss deleted file mode 100644 index 5b3c6b4990ff1..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'embedded_map'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/embedded_map.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/embedded_map.tsx deleted file mode 100644 index 7b0838329c0ca..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/embedded_map.tsx +++ /dev/null @@ -1,148 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useEffect, useRef, useState } from 'react'; - -import { htmlIdGenerator } from '@elastic/eui'; -import type { LayerDescriptor } from '@kbn/maps-plugin/common'; -import { INITIAL_LOCATION } from '@kbn/maps-plugin/common'; -import type { - MapEmbeddable, - MapEmbeddableInput, - MapEmbeddableOutput, -} from '@kbn/maps-plugin/public/embeddable'; -import type { RenderTooltipContentParams } from '@kbn/maps-plugin/public'; -import { MAP_SAVED_OBJECT_TYPE } from '@kbn/maps-plugin/public'; -import type { EmbeddableFactory, ErrorEmbeddable } from '@kbn/embeddable-plugin/public'; -import { isErrorEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; -import { useDataVisualizerKibana } from '../../../kibana_context'; -import './_embedded_map.scss'; - -export function EmbeddedMapComponent({ - layerList, - mapEmbeddableInput, - renderTooltipContent, -}: { - layerList: LayerDescriptor[]; - mapEmbeddableInput?: MapEmbeddableInput; - renderTooltipContent?: (params: RenderTooltipContentParams) => JSX.Element; -}) { - const [embeddable, setEmbeddable] = useState(); - - const embeddableRoot: React.RefObject = useRef(null); - const baseLayers = useRef(); - - const { - services: { embeddable: embeddablePlugin, maps: mapsPlugin, data }, - } = useDataVisualizerKibana(); - - const factory: - | EmbeddableFactory - | undefined = embeddablePlugin - ? embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE) - : undefined; - - // Update the layer list with updated geo points upon refresh - useEffect(() => { - async function updateIndexPatternSearchLayer() { - if ( - embeddable && - !isErrorEmbeddable(embeddable) && - Array.isArray(layerList) && - Array.isArray(baseLayers.current) - ) { - embeddable.setLayerList([...baseLayers.current, ...layerList]); - } - } - updateIndexPatternSearchLayer(); - }, [embeddable, layerList]); - - useEffect(() => { - async function setupEmbeddable() { - if (!factory) { - // eslint-disable-next-line no-console - console.error('Map embeddable not found.'); - return; - } - const input: MapEmbeddableInput = { - id: htmlIdGenerator()(), - attributes: { title: '' }, - filters: data.query.filterManager.getFilters() ?? [], - hidePanelTitles: true, - viewMode: ViewMode.VIEW, - isLayerTOCOpen: false, - hideFilterActions: true, - // can use mapSettings to center map on anomalies - mapSettings: { - disableInteractive: false, - hideToolbarOverlay: false, - hideLayerControl: false, - hideViewControl: false, - initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent - autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query - }, - }; - - const embeddableObject = await factory.create(input); - - if (embeddableObject && !isErrorEmbeddable(embeddableObject)) { - const basemapLayerDescriptor = mapsPlugin - ? await mapsPlugin.createLayerDescriptors.createBasemapLayerDescriptor() - : null; - - if (basemapLayerDescriptor) { - baseLayers.current = [basemapLayerDescriptor]; - await embeddableObject.setLayerList(baseLayers.current); - } - } - - setEmbeddable(embeddableObject); - } - - setupEmbeddable(); - // we want this effect to execute exactly once after the component mounts - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (embeddable && !isErrorEmbeddable(embeddable) && mapEmbeddableInput !== undefined) { - embeddable.updateInput(mapEmbeddableInput); - } - }, [embeddable, mapEmbeddableInput]); - - useEffect(() => { - if (embeddable && !isErrorEmbeddable(embeddable) && renderTooltipContent !== undefined) { - embeddable.setRenderTooltipContent(renderTooltipContent); - } - }, [embeddable, renderTooltipContent]); - - // We can only render after embeddable has already initialized - useEffect(() => { - if (embeddableRoot.current && embeddable) { - embeddable.render(embeddableRoot.current); - } - }, [embeddable, embeddableRoot]); - - if (!embeddablePlugin) { - // eslint-disable-next-line no-console - console.error('Embeddable start plugin not found'); - return null; - } - if (!mapsPlugin) { - // eslint-disable-next-line no-console - console.error('Maps start plugin not found'); - return null; - } - - return ( -
- ); -} diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/index.ts b/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/index.ts deleted file mode 100644 index ee11a18345f64..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/index.ts +++ /dev/null @@ -1,8 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { EmbeddedMapComponent } from './embedded_map'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/geo_point_content.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/geo_point_content.tsx index eec4522b96bd0..b1956f937d2cf 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/geo_point_content.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/geo_point_content.tsx @@ -10,15 +10,19 @@ import React, { useMemo } from 'react'; import type { Feature, Point } from 'geojson'; import type { FieldDataRowProps } from '../../stats_table/types/field_data_row'; import { DocumentStatsTable } from '../../stats_table/components/field_data_expanded_row/document_stats'; -import { EmbeddedMapComponent } from '../../embedded_map'; import { convertWKTGeoToLonLat, getGeoPointsLayer } from './format_utils'; import { ExpandedRowContent } from '../../stats_table/components/field_data_expanded_row/expanded_row_content'; import { ExamplesList } from '../../examples_list'; import { ExpandedRowPanel } from '../../stats_table/components/field_data_expanded_row/expanded_row_panel'; +import { useDataVisualizerKibana } from '../../../../kibana_context'; export const DEFAULT_GEO_REGEX = RegExp('(?.+) (?.+)'); export const GeoPointContent: FC = ({ config }) => { + const { + services: { maps: mapsService }, + } = useDataVisualizerKibana(); + const formattedResults = useMemo(() => { const { stats } = config; @@ -54,7 +58,7 @@ export const GeoPointContent: FC = ({ config }) => { if (geoPointsFeatures.length > 0) { return { examples: formattedExamples, - layerList: [getGeoPointsLayer(geoPointsFeatures)], + pointsLayer: getGeoPointsLayer(geoPointsFeatures), }; } } @@ -62,12 +66,10 @@ export const GeoPointContent: FC = ({ config }) => { return ( - {formattedResults && Array.isArray(formattedResults.examples) && ( - - )} - {formattedResults && Array.isArray(formattedResults.layerList) && ( + {formattedResults?.examples && } + {mapsService && formattedResults?.pointsLayer && ( - + )} diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx index 0dd142f827301..e8ee4bd99666a 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx @@ -5,9 +5,11 @@ * 2.0. */ import type { FC } from 'react'; +import { useMemo } from 'react'; import React, { useEffect, useState } from 'react'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { ES_GEO_FIELD_TYPE, LayerDescriptor } from '@kbn/maps-plugin/common'; +import { INITIAL_LOCATION } from '@kbn/maps-plugin/common'; import type { CombinedQuery } from '../../../../index_data_visualizer/types/combined_query'; import { ExpandedRowContent } from '../../stats_table/components/field_data_expanded_row/expanded_row_content'; import { DocumentStatsTable } from '../../stats_table/components/field_data_expanded_row/document_stats'; @@ -15,7 +17,6 @@ import { ExamplesList } from '../../examples_list'; import type { FieldVisConfig } from '../../stats_table/types'; import { useDataVisualizerKibana } from '../../../../kibana_context'; import { SUPPORTED_FIELD_TYPES } from '../../../../../../common/constants'; -import { EmbeddedMapComponent } from '../../embedded_map'; import { ExpandedRowPanel } from '../../stats_table/components/field_data_expanded_row/expanded_row_panel'; export const GeoPointContentWithMap: FC<{ @@ -31,84 +32,106 @@ export const GeoPointContentWithMap: FC<{ services: { maps: mapsPlugin, data }, } = useDataVisualizerKibana(); - // Update the layer list with updated geo points upon refresh - useEffect(() => { - async function updateIndexPatternSearchLayer() { - if ( - dataView?.id !== undefined && - config !== undefined && - config.fieldName !== undefined && - (config.type === SUPPORTED_FIELD_TYPES.GEO_POINT || - config.type === SUPPORTED_FIELD_TYPES.GEO_SHAPE) - ) { - const params = { - indexPatternId: dataView.id, - geoFieldName: config.fieldName, - geoFieldType: config.type as ES_GEO_FIELD_TYPE, - filters: data.query.filterManager.getFilters() ?? [], + const query = useMemo(() => { + return combinedQuery + ? { + query: combinedQuery.searchString, + language: combinedQuery.searchQueryLanguage, + } + : undefined; + }, [combinedQuery]); - ...(typeof esql === 'string' ? { esql, type: 'ESQL' } : {}), - ...(combinedQuery - ? { - query: { - query: combinedQuery.searchString, - language: combinedQuery.searchQueryLanguage, - }, - } - : {}), - }; - const searchLayerDescriptor = mapsPlugin - ? await mapsPlugin.createLayerDescriptors.createESSearchSourceLayerDescriptor(params) - : null; + useEffect(() => { + if (!mapsPlugin) { + return; + } - if (searchLayerDescriptor?.sourceDescriptor) { - if (esql !== undefined) { - // Currently, createESSearchSourceLayerDescriptor doesn't support ES|QL yet - // but we can manually override the source descriptor with the ES|QL ESQLSourceDescriptor - const esqlSourceDescriptor = { - columns: [ - { - name: config.fieldName, - type: config.type, - }, - ], - dataViewId: dataView.id, - dateField: dataView.timeFieldName ?? timeFieldName, - geoField: config.fieldName, - esql, - narrowByGlobalSearch: true, - narrowByGlobalTime: true, - narrowByMapBounds: true, - id: searchLayerDescriptor.sourceDescriptor.id, - type: 'ESQL', - applyForceRefresh: true, - }; + if ( + !dataView?.id || + !config?.fieldName || + !( + config.type === SUPPORTED_FIELD_TYPES.GEO_POINT || + config.type === SUPPORTED_FIELD_TYPES.GEO_SHAPE + ) + ) { + setLayerList([]); + return; + } - setLayerList([ - ...layerList, + let ignore = false; + mapsPlugin.createLayerDescriptors + .createESSearchSourceLayerDescriptor({ + indexPatternId: dataView.id, + geoFieldName: config.fieldName, + geoFieldType: config.type as ES_GEO_FIELD_TYPE, + }) + .then((searchLayerDescriptor) => { + if (ignore) { + return; + } + if (esql !== undefined) { + // Currently, createESSearchSourceLayerDescriptor doesn't support ES|QL yet + // but we can manually override the source descriptor with the ES|QL ESQLSourceDescriptor + const esqlSourceDescriptor = { + columns: [ { - ...searchLayerDescriptor, - sourceDescriptor: esqlSourceDescriptor, + name: config.fieldName, + type: config.type, }, - ]); - } else { - setLayerList([...layerList, searchLayerDescriptor]); - } + ], + dataViewId: dataView.id, + dateField: dataView.timeFieldName ?? timeFieldName, + geoField: config.fieldName, + esql, + narrowByGlobalSearch: true, + narrowByGlobalTime: true, + narrowByMapBounds: true, + id: searchLayerDescriptor.sourceDescriptor!.id, + type: 'ESQL', + applyForceRefresh: true, + }; + + setLayerList([ + { + ...searchLayerDescriptor, + sourceDescriptor: esqlSourceDescriptor, + }, + ]); + } else { + setLayerList([searchLayerDescriptor]); } - } - } - updateIndexPatternSearchLayer(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dataView, combinedQuery, esql, config, mapsPlugin, data.query]); + }) + .catch(() => { + if (!ignore) { + setLayerList([]); + } + }); + + return () => { + ignore = true; + }; + }, [dataView, combinedQuery, esql, config, mapsPlugin, timeFieldName]); if (stats?.examples === undefined) return null; return ( - - - + {mapsPlugin && ( + + + + )} ); }; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx index c73ac26938e85..e326f3a4f79f0 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx @@ -7,7 +7,7 @@ import type { FC } from 'react'; import React, { useMemo } from 'react'; -import { EuiText, htmlIdGenerator } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { VectorLayerDescriptor } from '@kbn/maps-plugin/common'; @@ -21,7 +21,6 @@ import { import type { EMSTermJoinConfig } from '@kbn/maps-plugin/public'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; import { useDataVisualizerKibana } from '../../../../../kibana_context'; -import { EmbeddedMapComponent } from '../../../embedded_map'; import type { FieldVisStats } from '../../../../../../../common/types'; import { ExpandedRowPanel } from './expanded_row_panel'; @@ -31,7 +30,7 @@ export const getChoroplethTopValuesLayer = ( { layerId, field }: EMSTermJoinConfig ): VectorLayerDescriptor => { return { - id: htmlIdGenerator()(), + id: 'choroplethLayer', label: i18n.translate('xpack.dataVisualizer.choroplethMap.topValuesCount', { defaultMessage: 'Top values count for {fieldName}', values: { fieldName }, @@ -41,7 +40,7 @@ export const getChoroplethTopValuesLayer = ( // Left join is the id from the type of field (e.g. world_countries) leftField: field, right: { - id: 'anomaly_count', + id: 'doc_count', type: SOURCE_TYPES.TABLE_SOURCE, __rows: topValues, __columns: [ @@ -103,13 +102,14 @@ export const ChoroplethMap: FC = ({ stats, suggestion }) => { const { services: { data: { fieldFormats }, + maps: mapsService, }, } = useDataVisualizerKibana(); const { fieldName, isTopValuesSampled, topValues, sampleCount } = stats; - const layerList: VectorLayerDescriptor[] = useMemo( - () => [getChoroplethTopValuesLayer(fieldName || '', topValues || [], suggestion)], + const choroplethLayer: VectorLayerDescriptor = useMemo( + () => getChoroplethTopValuesLayer(fieldName || '', topValues || [], suggestion), [suggestion, fieldName, topValues] ); @@ -157,9 +157,11 @@ export const ChoroplethMap: FC = ({ stats, suggestion }) => { className={'dvPanel__wrapper'} grow={true} > -
- -
+ {mapsService && ( +
+ +
+ )} {countsElement} diff --git a/x-pack/plugins/maps/public/api/start_api.ts b/x-pack/plugins/maps/public/api/start_api.ts index eea440b8b2afc..976a7ec4da084 100644 --- a/x-pack/plugins/maps/public/api/start_api.ts +++ b/x-pack/plugins/maps/public/api/start_api.ts @@ -8,6 +8,8 @@ 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 '../embeddable/map_component'; export interface MapsStartApi { createLayerDescriptors: { @@ -20,5 +22,7 @@ export interface MapsStartApi { params: CreateLayerDescriptorParams ) => Promise; }; + Map: React.FC; + PassiveMap: React.FC; suggestEMSTermJoinConfig(config: SampleValuesConfig): Promise; } diff --git a/x-pack/plugins/maps/public/embeddable/map_component.tsx b/x-pack/plugins/maps/public/embeddable/map_component.tsx index 7d1a3ed734737..a9f3fe4afbc0b 100644 --- a/x-pack/plugins/maps/public/embeddable/map_component.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_component.tsx @@ -8,19 +8,21 @@ import React, { Component, RefObject } from 'react'; import { first } from 'rxjs'; import { v4 as uuidv4 } from 'uuid'; -import type { Filter } from '@kbn/es-query'; -import type { Query, TimeRange } from '@kbn/es-query'; -import type { LayerDescriptor, MapCenterAndZoom } from '../../common/descriptor_types'; -import type { MapEmbeddableType } from './types'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import type { LayerDescriptor, MapCenterAndZoom, MapSettings } from '../../common/descriptor_types'; import { MapEmbeddable } from './map_embeddable'; import { createBasemapLayerDescriptor } from '../classes/layers/create_basemap_layer_descriptor'; -interface Props { - title: string; +export interface Props { + title?: string; filters?: Filter[]; query?: Query; timeRange?: TimeRange; - getLayerDescriptors: () => LayerDescriptor[]; + layerList: LayerDescriptor[]; + mapSettings?: Partial; + hideFilterActions?: boolean; + isLayerTOCOpen?: boolean; mapCenter?: MapCenterAndZoom; onInitialRenderComplete?: () => void; /* @@ -30,11 +32,13 @@ interface Props { } export class MapComponent extends Component { - private _mapEmbeddable: MapEmbeddableType; + private _prevLayerList: LayerDescriptor[]; + private _mapEmbeddable: MapEmbeddable; private readonly _embeddableRef: RefObject = React.createRef(); constructor(props: Props) { super(props); + this._prevLayerList = this.props.layerList; this._mapEmbeddable = new MapEmbeddable( { editable: false, @@ -42,15 +46,24 @@ export class MapComponent extends Component { { id: uuidv4(), attributes: { - title: this.props.title, - layerListJSON: JSON.stringify([ - createBasemapLayerDescriptor(), - ...this.props.getLayerDescriptors(), - ]), + title: this.props.title ?? '', + layerListJSON: JSON.stringify(this.getLayerList()), }, + hidePanelTitles: !Boolean(this.props.title), + viewMode: ViewMode.VIEW, + isLayerTOCOpen: + typeof this.props.isLayerTOCOpen === 'boolean' ? this.props.isLayerTOCOpen : false, + hideFilterActions: + typeof this.props.hideFilterActions === 'boolean' ? this.props.hideFilterActions : false, mapCenter: this.props.mapCenter, + mapSettings: this.props.mapSettings ?? {}, } ); + this._mapEmbeddable.updateInput({ + filters: this.props.filters, + query: this.props.query, + timeRange: this.props.timeRange, + }); if (this.props.onInitialRenderComplete) { this._mapEmbeddable @@ -84,6 +97,16 @@ export class MapComponent extends Component { query: this.props.query, timeRange: this.props.timeRange, }); + + if (this._prevLayerList !== this.props.layerList) { + this._mapEmbeddable.setLayerList(this.getLayerList()); + this._prevLayerList = this.props.layerList; + } + } + + getLayerList(): LayerDescriptor[] { + const basemapLayer = createBasemapLayerDescriptor(); + return basemapLayer ? [basemapLayer, ...this.props.layerList] : this.props.layerList; } render() { diff --git a/x-pack/plugins/maps/public/embeddable/map_component_lazy.tsx b/x-pack/plugins/maps/public/embeddable/map_component_lazy.tsx new file mode 100644 index 0000000000000..2fff281a23ffd --- /dev/null +++ b/x-pack/plugins/maps/public/embeddable/map_component_lazy.tsx @@ -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 React from 'react'; +import { dynamic } from '@kbn/shared-ux-utility'; +import type { Props } from './map_component'; + +const Component = dynamic(async () => { + const { MapComponent } = await import('./map_component'); + return { + default: MapComponent, + }; +}); + +export function MapComponentLazy(props: Props) { + return ; +} diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx index 0baa70ead96e4..66c623bf524a7 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx @@ -5,16 +5,19 @@ * 2.0. */ -import React, { lazy } from 'react'; +import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import type { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common'; +import { dynamic } from '@kbn/shared-ux-utility'; import type { RegionMapVisRenderValue } from './region_map_fn'; -import { LazyWrapper } from '../../lazy_wrapper'; import { REGION_MAP_RENDER } from './types'; -const getLazyComponent = () => { - return lazy(() => import('./region_map_visualization')); -}; +const Component = dynamic(async () => { + const { RegionMapVisualization } = await import('./region_map_visualization'); + return { + default: RegionMapVisualization, + }; +}); export const regionMapRenderer = { name: REGION_MAP_RENDER, @@ -34,6 +37,6 @@ export const regionMapRenderer = { visConfig, }; - render(, domNode); + render(, domNode); }, } as ExpressionRenderDefinition; 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 4dedb97202857..701b7baf002b2 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 @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import type { Filter } from '@kbn/es-query'; import type { Query, TimeRange } from '@kbn/es-query'; import { RegionMapVisConfig } from './types'; @@ -20,30 +20,33 @@ interface Props { onInitialRenderComplete: () => void; } -function RegionMapVisualization(props: Props) { - const mapCenter = { - lat: props.visConfig.mapCenter[0], - lon: props.visConfig.mapCenter[1], - zoom: props.visConfig.mapZoom, - }; - function getLayerDescriptors() { +export function RegionMapVisualization(props: Props) { + const initialMapCenter = useMemo(() => { + return { + lat: props.visConfig.mapCenter[0], + lon: props.visConfig.mapCenter[1], + zoom: props.visConfig.mapZoom, + }; + // props.visConfig reference changes each render but values are the same + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const initialLayerList = useMemo(() => { const layerDescriptor = createRegionMapLayerDescriptor(props.visConfig.layerDescriptorParams); return layerDescriptor ? [layerDescriptor] : []; - } + // props.visConfig reference changes each render but values are the same + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return ( ); } - -// default export required for React.Lazy -// eslint-disable-next-line import/no-default-export -export default RegionMapVisualization; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx index 233bd9350e2e7..e784cf340f420 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx @@ -5,16 +5,19 @@ * 2.0. */ -import React, { lazy } from 'react'; +import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import type { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common'; +import { dynamic } from '@kbn/shared-ux-utility'; import type { TileMapVisRenderValue } from './tile_map_fn'; -import { LazyWrapper } from '../../lazy_wrapper'; import { TILE_MAP_RENDER } from './types'; -const getLazyComponent = () => { - return lazy(() => import('./tile_map_visualization')); -}; +const Component = dynamic(async () => { + const { TileMapVisualization } = await import('./tile_map_visualization'); + return { + default: TileMapVisualization, + }; +}); export const tileMapRenderer = { name: TILE_MAP_RENDER, @@ -34,6 +37,6 @@ export const tileMapRenderer = { visConfig, }; - render(, domNode); + render(, domNode); }, } as ExpressionRenderDefinition; 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 5eb4132528de5..f8f9c74360e8c 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 @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import type { Filter } from '@kbn/es-query'; import type { Query, TimeRange } from '@kbn/es-query'; import type { TileMapVisConfig } from './types'; @@ -20,30 +20,33 @@ interface Props { onInitialRenderComplete: () => void; } -function TileMapVisualization(props: Props) { - const mapCenter = { - lat: props.visConfig.mapCenter[0], - lon: props.visConfig.mapCenter[1], - zoom: props.visConfig.mapZoom, - }; - function getLayerDescriptors() { +export function TileMapVisualization(props: Props) { + const initialMapCenter = useMemo(() => { + return { + lat: props.visConfig.mapCenter[0], + lon: props.visConfig.mapCenter[1], + zoom: props.visConfig.mapZoom, + }; + // props.visConfig reference changes each render but values are the same + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const initialLayerList = useMemo(() => { const layerDescriptor = createTileMapLayerDescriptor(props.visConfig.layerDescriptorParams); return layerDescriptor ? [layerDescriptor] : []; - } + // props.visConfig reference changes each render but values are the same + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return ( ); } - -// default export required for React.Lazy -// eslint-disable-next-line import/no-default-export -export default TileMapVisualization; diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/choropleth_chart.tsx b/x-pack/plugins/maps/public/lens/choropleth_chart/choropleth_chart.tsx index 94fb1adc2dfd6..7c0613f1a7fa1 100644 --- a/x-pack/plugins/maps/public/lens/choropleth_chart/choropleth_chart.tsx +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/choropleth_chart.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import type { FileLayer } from '@elastic/ems-client'; import { IUiSettingsClient } from '@kbn/core/public'; -import type { EmbeddableFactory } from '@kbn/embeddable-plugin/public'; import type { Datatable } from '@kbn/expressions-plugin/public'; import type { FormatFactory } from '@kbn/field-formats-plugin/common'; import { @@ -24,13 +23,11 @@ import { emsWorldLayerId } from '../../../common/constants'; import { ChoroplethChartProps } from './types'; import { getEmsSuggestion } from './get_ems_suggestion'; import { PassiveMap } from '../passive_map'; -import type { MapEmbeddableInput, MapEmbeddableOutput } from '../../embeddable'; interface Props extends ChoroplethChartProps { formatFactory: FormatFactory; uiSettings: IUiSettingsClient; emsFileLayers: FileLayer[]; - mapEmbeddableFactory: EmbeddableFactory; onRenderComplete: () => void; } @@ -40,7 +37,6 @@ export function ChoroplethChart({ formatFactory, uiSettings, emsFileLayers, - mapEmbeddableFactory, onRenderComplete, }: Props) { if (!args.regionAccessor || !args.valueAccessor) { @@ -130,13 +126,7 @@ export function ChoroplethChart({ type: LAYER_TYPE.GEOJSON_VECTOR, }; - return ( - - ); + return ; } function getAccessorLabel(table: Datatable, accessor: string) { diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/expression_renderer.tsx b/x-pack/plugins/maps/public/lens/choropleth_chart/expression_renderer.tsx index 059d612883d82..5bb15ed1a7c35 100644 --- a/x-pack/plugins/maps/public/lens/choropleth_chart/expression_renderer.tsx +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/expression_renderer.tsx @@ -8,7 +8,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import type { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public'; -import type { EmbeddableFactory } from '@kbn/embeddable-plugin/public'; import { METRIC_TYPE } from '@kbn/analytics'; import type { CoreSetup, CoreStart } from '@kbn/core/public'; import type { FileLayer } from '@elastic/ems-client'; @@ -16,7 +15,6 @@ import type { KibanaExecutionContext } from '@kbn/core-execution-context-common' import { ChartSizeEvent } from '@kbn/chart-expressions-common'; import type { MapsPluginStartDependencies } from '../../plugin'; import type { ChoroplethChartProps } from './types'; -import type { MapEmbeddableInput, MapEmbeddableOutput } from '../../embeddable'; export const RENDERER_ID = 'lens_choropleth_chart_renderer'; @@ -65,13 +63,6 @@ export function getExpressionRenderer(coreSetup: CoreSetup; - if (!mapEmbeddableFactory) { - return; - } - let emsFileLayers: FileLayer[] = []; try { emsFileLayers = await getEmsFileLayers(); @@ -111,7 +102,6 @@ export function getExpressionRenderer(coreSetup: CoreSetup, domNode diff --git a/x-pack/plugins/maps/public/lens/index.ts b/x-pack/plugins/maps/public/lens/index.ts index 1af581ca196f0..744153ba5e736 100644 --- a/x-pack/plugins/maps/public/lens/index.ts +++ b/x-pack/plugins/maps/public/lens/index.ts @@ -6,3 +6,4 @@ */ export { setupLensChoroplethChart } from './choropleth_chart'; +export { PassiveMapLazy } from './passive_map_lazy'; diff --git a/x-pack/plugins/maps/public/lens/passive_map.tsx b/x-pack/plugins/maps/public/lens/passive_map.tsx index 06450ee16f501..31e90ee8e91f8 100644 --- a/x-pack/plugins/maps/public/lens/passive_map.tsx +++ b/x-pack/plugins/maps/public/lens/passive_map.tsx @@ -9,16 +9,15 @@ import React, { Component, RefObject } from 'react'; import { Subscription } from 'rxjs'; import { v4 as uuidv4 } from 'uuid'; import { EuiLoadingChart } from '@elastic/eui'; -import { EmbeddableFactory, ViewMode } from '@kbn/embeddable-plugin/public'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; import type { LayerDescriptor } from '../../common/descriptor_types'; import { INITIAL_LOCATION } from '../../common'; -import { MapEmbeddable, MapEmbeddableInput, MapEmbeddableOutput } from '../embeddable'; +import { MapEmbeddable } from '../embeddable'; import { createBasemapLayerDescriptor } from '../classes/layers/create_basemap_layer_descriptor'; -interface Props { - factory: EmbeddableFactory; +export interface Props { passiveLayer: LayerDescriptor; - onRenderComplete: () => void; + onRenderComplete?: () => void; } interface State { @@ -65,37 +64,40 @@ export class PassiveMap extends Component { async _setupEmbeddable() { const basemapLayerDescriptor = createBasemapLayerDescriptor(); const intialLayers = basemapLayerDescriptor ? [basemapLayerDescriptor] : []; - const mapEmbeddable = (await this.props.factory.create({ - id: uuidv4(), - attributes: { - title: '', - layerListJSON: JSON.stringify([...intialLayers, this.props.passiveLayer]), + const mapEmbeddable = new MapEmbeddable( + { + editable: false, }, - filters: [], - hidePanelTitles: true, - viewMode: ViewMode.VIEW, - isLayerTOCOpen: false, - hideFilterActions: true, - mapSettings: { - disableInteractive: false, - hideToolbarOverlay: false, - hideLayerControl: false, - hideViewControl: false, - initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent - autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query - }, - })) as MapEmbeddable | undefined; + { + id: uuidv4(), + attributes: { + title: '', + layerListJSON: JSON.stringify([...intialLayers, this.props.passiveLayer]), + }, + filters: [], + hidePanelTitles: true, + viewMode: ViewMode.VIEW, + isLayerTOCOpen: false, + hideFilterActions: true, + mapSettings: { + disableInteractive: false, + hideToolbarOverlay: false, + hideLayerControl: false, + hideViewControl: false, + initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent + autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query + }, + } + ); - if (!mapEmbeddable) { - return; + if (this.props.onRenderComplete) { + this._onRenderSubscription = mapEmbeddable.getOnRenderComplete$().subscribe(() => { + if (this._isMounted && this.props.onRenderComplete) { + this.props.onRenderComplete(); + } + }); } - this._onRenderSubscription = mapEmbeddable.getOnRenderComplete$().subscribe(() => { - if (this._isMounted) { - this.props.onRenderComplete(); - } - }); - if (this._isMounted) { mapEmbeddable.setIsSharable(false); this.setState({ mapEmbeddable }, () => { diff --git a/x-pack/plugins/maps/public/lens/passive_map_lazy.tsx b/x-pack/plugins/maps/public/lens/passive_map_lazy.tsx new file mode 100644 index 0000000000000..5929f77a5f943 --- /dev/null +++ b/x-pack/plugins/maps/public/lens/passive_map_lazy.tsx @@ -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 React from 'react'; +import { dynamic } from '@kbn/shared-ux-utility'; +import type { Props } from './passive_map'; + +const Component = dynamic(async () => { + const { PassiveMap } = await import('./passive_map'); + return { + default: PassiveMap, + }; +}); + +export function PassiveMapLazy(props: Props) { + return ; +} diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 4fb9bc6cd231d..2eaabaa00aaf6 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -90,10 +90,11 @@ import { import { MapInspectorView } from './inspector/map_adapter/map_inspector_view'; import { VectorTileInspectorView } from './inspector/vector_tile_adapter/vector_tile_inspector_view'; -import { setupLensChoroplethChart } from './lens'; +import { PassiveMapLazy, setupLensChoroplethChart } from './lens'; import { CONTENT_ID, LATEST_VERSION, MapAttributes } from '../common/content_management'; import { savedObjectToEmbeddableAttributes } from './map_attribute_service'; import { MapByValueInput } from './embeddable'; +import { MapComponentLazy } from './embeddable/map_component_lazy'; export interface MapsPluginSetupDependencies { cloud?: CloudSetup; @@ -275,6 +276,8 @@ export class MapsPlugin return { createLayerDescriptors, suggestEMSTermJoinConfig, + Map: MapComponentLazy, + PassiveMap: PassiveMapLazy, }; } } diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index 8e725ff81cb0f..0ce9152b016eb 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -87,7 +87,8 @@ "@kbn/presentation-publishing", "@kbn/saved-objects-finder-plugin", "@kbn/esql-utils", - "@kbn/apm-data-view" + "@kbn/apm-data-view", + "@kbn/shared-ux-utility" ], "exclude": [ "target/**/*", diff --git a/x-pack/test/functional/services/ml/data_visualizer_table.ts b/x-pack/test/functional/services/ml/data_visualizer_table.ts index d0d56da9091d0..a6f936e43bf37 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_table.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_table.ts @@ -497,9 +497,7 @@ export function MachineLearningDataVisualizerTableProvider( await this.assertExamplesList(fieldName, expectedExamplesCount); - await testSubjects.existOrFail( - this.detailsSelector(fieldName, 'dataVisualizerEmbeddedMapContent') - ); + await testSubjects.existOrFail(this.detailsSelector(fieldName, 'mapContainer')); await this.ensureDetailsClosed(fieldName); }