mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[ML] Decouple data_visualizer from MapEmbeddable (#181928)
Part of https://github.com/elastic/kibana/issues/182020 ### test instructions 1. install web logs sample data 2. Open discover 3. In table, click "Field statistics" 4. Expand `geo.dest` row. Verify choropleth map is displayed. 5. Expand `geo.coordinates` row. Verify vector map is displayed. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
5ba6a399f2
commit
1e8d51ae51
22 changed files with 294 additions and 366 deletions
|
@ -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
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
@import 'embedded_map';
|
|
@ -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<ErrorEmbeddable | MapEmbeddable | undefined>();
|
||||
|
||||
const embeddableRoot: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
|
||||
const baseLayers = useRef<LayerDescriptor[]>();
|
||||
|
||||
const {
|
||||
services: { embeddable: embeddablePlugin, maps: mapsPlugin, data },
|
||||
} = useDataVisualizerKibana();
|
||||
|
||||
const factory:
|
||||
| EmbeddableFactory<MapEmbeddableInput, MapEmbeddableOutput, MapEmbeddable>
|
||||
| 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 (
|
||||
<div
|
||||
data-test-subj="dataVisualizerEmbeddedMapContent"
|
||||
className="embeddedMap__content"
|
||||
ref={embeddableRoot}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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';
|
|
@ -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('(?<lat>.+) (?<lon>.+)');
|
||||
|
||||
export const GeoPointContent: FC<FieldDataRowProps> = ({ config }) => {
|
||||
const {
|
||||
services: { maps: mapsService },
|
||||
} = useDataVisualizerKibana();
|
||||
|
||||
const formattedResults = useMemo(() => {
|
||||
const { stats } = config;
|
||||
|
||||
|
@ -54,7 +58,7 @@ export const GeoPointContent: FC<FieldDataRowProps> = ({ config }) => {
|
|||
if (geoPointsFeatures.length > 0) {
|
||||
return {
|
||||
examples: formattedExamples,
|
||||
layerList: [getGeoPointsLayer(geoPointsFeatures)],
|
||||
pointsLayer: getGeoPointsLayer(geoPointsFeatures),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -62,12 +66,10 @@ export const GeoPointContent: FC<FieldDataRowProps> = ({ config }) => {
|
|||
return (
|
||||
<ExpandedRowContent dataTestSubj={'dataVisualizerGeoPointContent'}>
|
||||
<DocumentStatsTable config={config} />
|
||||
{formattedResults && Array.isArray(formattedResults.examples) && (
|
||||
<ExamplesList examples={formattedResults.examples} />
|
||||
)}
|
||||
{formattedResults && Array.isArray(formattedResults.layerList) && (
|
||||
{formattedResults?.examples && <ExamplesList examples={formattedResults.examples} />}
|
||||
{mapsService && formattedResults?.pointsLayer && (
|
||||
<ExpandedRowPanel className={'dvPanel__wrapper dvMap__wrapper'} grow={true}>
|
||||
<EmbeddedMapComponent layerList={formattedResults.layerList} />
|
||||
<mapsService.PassiveMap passiveLayer={formattedResults.pointsLayer} />
|
||||
</ExpandedRowPanel>
|
||||
)}
|
||||
</ExpandedRowContent>
|
||||
|
|
|
@ -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() ?? [],
|
||||
|
||||
...(typeof esql === 'string' ? { esql, type: 'ESQL' } : {}),
|
||||
...(combinedQuery
|
||||
? {
|
||||
query: {
|
||||
query: combinedQuery.searchString,
|
||||
language: combinedQuery.searchQueryLanguage,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
const searchLayerDescriptor = mapsPlugin
|
||||
? await mapsPlugin.createLayerDescriptors.createESSearchSourceLayerDescriptor(params)
|
||||
: null;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
setLayerList([
|
||||
...layerList,
|
||||
{
|
||||
...searchLayerDescriptor,
|
||||
sourceDescriptor: esqlSourceDescriptor,
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
setLayerList([...layerList, searchLayerDescriptor]);
|
||||
}
|
||||
const query = useMemo(() => {
|
||||
return combinedQuery
|
||||
? {
|
||||
query: combinedQuery.searchString,
|
||||
language: combinedQuery.searchQueryLanguage,
|
||||
}
|
||||
}
|
||||
: undefined;
|
||||
}, [combinedQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mapsPlugin) {
|
||||
return;
|
||||
}
|
||||
updateIndexPatternSearchLayer();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataView, combinedQuery, esql, config, mapsPlugin, data.query]);
|
||||
|
||||
if (
|
||||
!dataView?.id ||
|
||||
!config?.fieldName ||
|
||||
!(
|
||||
config.type === SUPPORTED_FIELD_TYPES.GEO_POINT ||
|
||||
config.type === SUPPORTED_FIELD_TYPES.GEO_SHAPE
|
||||
)
|
||||
) {
|
||||
setLayerList([]);
|
||||
return;
|
||||
}
|
||||
|
||||
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: [
|
||||
{
|
||||
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,
|
||||
};
|
||||
|
||||
setLayerList([
|
||||
{
|
||||
...searchLayerDescriptor,
|
||||
sourceDescriptor: esqlSourceDescriptor,
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
setLayerList([searchLayerDescriptor]);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (!ignore) {
|
||||
setLayerList([]);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [dataView, combinedQuery, esql, config, mapsPlugin, timeFieldName]);
|
||||
|
||||
if (stats?.examples === undefined) return null;
|
||||
return (
|
||||
<ExpandedRowContent dataTestSubj={'dataVisualizerIndexBasedMapContent'}>
|
||||
<DocumentStatsTable config={config} />
|
||||
<ExamplesList examples={stats?.examples} />
|
||||
<ExpandedRowPanel className={'dvPanel__wrapper dvMap__wrapper'} grow={true}>
|
||||
<EmbeddedMapComponent layerList={layerList} />
|
||||
</ExpandedRowPanel>
|
||||
{mapsPlugin && (
|
||||
<ExpandedRowPanel className={'dvPanel__wrapper dvMap__wrapper'} grow={true}>
|
||||
<mapsPlugin.Map
|
||||
layerList={layerList}
|
||||
hideFilterActions={true}
|
||||
mapSettings={{
|
||||
initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS,
|
||||
autoFitToDataBounds: true,
|
||||
}}
|
||||
filters={data.query.filterManager.getFilters()}
|
||||
query={query}
|
||||
timeRange={data.query.timefilter.timefilter.getTime()}
|
||||
/>
|
||||
</ExpandedRowPanel>
|
||||
)}
|
||||
</ExpandedRowContent>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<Props> = ({ 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<Props> = ({ stats, suggestion }) => {
|
|||
className={'dvPanel__wrapper'}
|
||||
grow={true}
|
||||
>
|
||||
<div className={'dvMap__wrapper'}>
|
||||
<EmbeddedMapComponent layerList={layerList} />
|
||||
</div>
|
||||
{mapsService && (
|
||||
<div className={'dvMap__wrapper'}>
|
||||
<mapsService.PassiveMap passiveLayer={choroplethLayer} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{countsElement}
|
||||
</ExpandedRowPanel>
|
||||
|
|
|
@ -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<LayerDescriptor>;
|
||||
};
|
||||
Map: React.FC<MapProps>;
|
||||
PassiveMap: React.FC<PassiveMapProps>;
|
||||
suggestEMSTermJoinConfig(config: SampleValuesConfig): Promise<EMSTermJoinConfig | null>;
|
||||
}
|
||||
|
|
|
@ -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<MapSettings>;
|
||||
hideFilterActions?: boolean;
|
||||
isLayerTOCOpen?: boolean;
|
||||
mapCenter?: MapCenterAndZoom;
|
||||
onInitialRenderComplete?: () => void;
|
||||
/*
|
||||
|
@ -30,11 +32,13 @@ interface Props {
|
|||
}
|
||||
|
||||
export class MapComponent extends Component<Props> {
|
||||
private _mapEmbeddable: MapEmbeddableType;
|
||||
private _prevLayerList: LayerDescriptor[];
|
||||
private _mapEmbeddable: MapEmbeddable;
|
||||
private readonly _embeddableRef: RefObject<HTMLDivElement> = React.createRef<HTMLDivElement>();
|
||||
|
||||
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<Props> {
|
|||
{
|
||||
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<Props> {
|
|||
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() {
|
||||
|
|
21
x-pack/plugins/maps/public/embeddable/map_component_lazy.tsx
Normal file
21
x-pack/plugins/maps/public/embeddable/map_component_lazy.tsx
Normal file
|
@ -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 <Component {...props} />;
|
||||
}
|
|
@ -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(<LazyWrapper getLazyComponent={getLazyComponent} lazyComponentProps={props} />, domNode);
|
||||
render(<Component {...props} />, domNode);
|
||||
},
|
||||
} as ExpressionRenderDefinition<RegionMapVisRenderValue>;
|
||||
|
|
|
@ -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 (
|
||||
<MapComponent
|
||||
title={props.visConfig.layerDescriptorParams.label}
|
||||
filters={props.filters}
|
||||
query={props.query}
|
||||
timeRange={props.timeRange}
|
||||
mapCenter={mapCenter}
|
||||
getLayerDescriptors={getLayerDescriptors}
|
||||
mapCenter={initialMapCenter}
|
||||
isLayerTOCOpen={true}
|
||||
layerList={initialLayerList}
|
||||
onInitialRenderComplete={props.onInitialRenderComplete}
|
||||
isSharable={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// default export required for React.Lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default RegionMapVisualization;
|
||||
|
|
|
@ -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(<LazyWrapper getLazyComponent={getLazyComponent} lazyComponentProps={props} />, domNode);
|
||||
render(<Component {...props} />, domNode);
|
||||
},
|
||||
} as ExpressionRenderDefinition<TileMapVisRenderValue>;
|
||||
|
|
|
@ -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 (
|
||||
<MapComponent
|
||||
title={props.visConfig.layerDescriptorParams.label}
|
||||
filters={props.filters}
|
||||
query={props.query}
|
||||
timeRange={props.timeRange}
|
||||
mapCenter={mapCenter}
|
||||
getLayerDescriptors={getLayerDescriptors}
|
||||
mapCenter={initialMapCenter}
|
||||
isLayerTOCOpen={true}
|
||||
layerList={initialLayerList}
|
||||
onInitialRenderComplete={props.onInitialRenderComplete}
|
||||
isSharable={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// default export required for React.Lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default TileMapVisualization;
|
||||
|
|
|
@ -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<MapEmbeddableInput, MapEmbeddableOutput>;
|
||||
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 (
|
||||
<PassiveMap
|
||||
passiveLayer={choroplethLayer}
|
||||
factory={mapEmbeddableFactory}
|
||||
onRenderComplete={onRenderComplete}
|
||||
/>
|
||||
);
|
||||
return <PassiveMap passiveLayer={choroplethLayer} onRenderComplete={onRenderComplete} />;
|
||||
}
|
||||
|
||||
function getAccessorLabel(table: Datatable, accessor: string) {
|
||||
|
|
|
@ -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<MapsPluginStartDepend
|
|||
const { ChoroplethChart } = await import('./choropleth_chart');
|
||||
const { getEmsFileLayers } = await import('../../util');
|
||||
|
||||
const mapEmbeddableFactory = plugins.embeddable.getEmbeddableFactory(
|
||||
'map'
|
||||
) as EmbeddableFactory<MapEmbeddableInput, MapEmbeddableOutput>;
|
||||
if (!mapEmbeddableFactory) {
|
||||
return;
|
||||
}
|
||||
|
||||
let emsFileLayers: FileLayer[] = [];
|
||||
try {
|
||||
emsFileLayers = await getEmsFileLayers();
|
||||
|
@ -111,7 +102,6 @@ export function getExpressionRenderer(coreSetup: CoreSetup<MapsPluginStartDepend
|
|||
formatFactory={plugins.fieldFormats.deserialize}
|
||||
uiSettings={coreStart.uiSettings}
|
||||
emsFileLayers={emsFileLayers}
|
||||
mapEmbeddableFactory={mapEmbeddableFactory}
|
||||
onRenderComplete={renderComplete}
|
||||
/>,
|
||||
domNode
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
*/
|
||||
|
||||
export { setupLensChoroplethChart } from './choropleth_chart';
|
||||
export { PassiveMapLazy } from './passive_map_lazy';
|
||||
|
|
|
@ -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<MapEmbeddableInput, MapEmbeddableOutput>;
|
||||
export interface Props {
|
||||
passiveLayer: LayerDescriptor;
|
||||
onRenderComplete: () => void;
|
||||
onRenderComplete?: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -65,36 +64,39 @@ export class PassiveMap extends Component<Props, State> {
|
|||
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;
|
||||
|
||||
if (!mapEmbeddable) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._onRenderSubscription = mapEmbeddable.getOnRenderComplete$().subscribe(() => {
|
||||
if (this._isMounted) {
|
||||
this.props.onRenderComplete();
|
||||
{
|
||||
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 (this.props.onRenderComplete) {
|
||||
this._onRenderSubscription = mapEmbeddable.getOnRenderComplete$().subscribe(() => {
|
||||
if (this._isMounted && this.props.onRenderComplete) {
|
||||
this.props.onRenderComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this._isMounted) {
|
||||
mapEmbeddable.setIsSharable(false);
|
||||
|
|
21
x-pack/plugins/maps/public/lens/passive_map_lazy.tsx
Normal file
21
x-pack/plugins/maps/public/lens/passive_map_lazy.tsx
Normal file
|
@ -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 <Component {...props} />;
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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/**/*",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue