mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[ML] decouple ML plugin from Map embeddable (#182409)
Part of https://github.com/elastic/kibana/issues/182020 --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
9ffe1df25e
commit
8c20433bd0
10 changed files with 53 additions and 185 deletions
|
@ -26,6 +26,7 @@ export type {
|
|||
} from './classes/tooltips/tooltip_property';
|
||||
|
||||
export type { MapsSetupApi, MapsStartApi } from './api';
|
||||
export type { CreateLayerDescriptorParams } from './classes/sources/es_search_source/create_layer_descriptor';
|
||||
|
||||
export type { MapEmbeddable, MapEmbeddableInput, MapEmbeddableOutput } from './embeddable';
|
||||
export { type MapApi, isMapApi } from './embeddable/map_api';
|
||||
|
|
|
@ -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 { MlEmbeddedMapComponent } from './ml_embedded_map';
|
|
@ -1,154 +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, MAP_SAVED_OBJECT_TYPE } from '@kbn/maps-plugin/common';
|
||||
import type {
|
||||
MapEmbeddable,
|
||||
MapEmbeddableInput,
|
||||
MapEmbeddableOutput,
|
||||
RenderTooltipContentParams,
|
||||
} from '@kbn/maps-plugin/public';
|
||||
|
||||
import type { EmbeddableFactory, ErrorEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||
import { isErrorEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { useMlKibana } from '../../contexts/kibana';
|
||||
|
||||
export function MlEmbeddedMapComponent({
|
||||
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 },
|
||||
} = useMlKibana();
|
||||
|
||||
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: [],
|
||||
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="mlEmbeddedMapContent"
|
||||
css={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flex: '1 1 100%',
|
||||
zIndex: 1,
|
||||
minHeight: 0, // Absolute must for Firefox to scroll contents
|
||||
}}
|
||||
ref={embeddableRoot}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -18,6 +18,7 @@ import {
|
|||
htmlIdGenerator,
|
||||
} from '@elastic/eui';
|
||||
import type { VectorLayerDescriptor } from '@kbn/maps-plugin/common';
|
||||
import { INITIAL_LOCATION } from '@kbn/maps-plugin/common';
|
||||
import {
|
||||
FIELD_ORIGIN,
|
||||
LAYER_TYPE,
|
||||
|
@ -29,7 +30,6 @@ import type { EMSTermJoinConfig } from '@kbn/maps-plugin/public';
|
|||
import { isDefined } from '@kbn/ml-is-defined';
|
||||
import type { MlAnomaliesTableRecord } from '@kbn/ml-anomaly-utils';
|
||||
import { useMlKibana } from '../contexts/kibana';
|
||||
import { MlEmbeddedMapComponent } from '../components/ml_embedded_map';
|
||||
|
||||
const MAX_ENTITY_VALUES = 3;
|
||||
|
||||
|
@ -253,7 +253,16 @@ export const AnomaliesMap: FC<Props> = ({ anomalies, jobIds }) => {
|
|||
data-test-subj="mlAnomalyExplorerAnomaliesMap"
|
||||
style={{ width: '100%', height: 300 }}
|
||||
>
|
||||
<MlEmbeddedMapComponent layerList={layerList} />
|
||||
{mapsPlugin && (
|
||||
<mapsPlugin.Map
|
||||
layerList={layerList}
|
||||
hideFilterActions={true}
|
||||
mapSettings={{
|
||||
initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS,
|
||||
autoFitToDataBounds: true,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</EuiAccordion>
|
||||
</EuiPanel>
|
||||
|
|
|
@ -7,14 +7,19 @@
|
|||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import type { LayerDescriptor } from '@kbn/maps-plugin/common';
|
||||
import { INITIAL_LOCATION } from '@kbn/maps-plugin/common';
|
||||
import type { Dictionary } from '../../../../common/types/common';
|
||||
import { getMLAnomaliesActualLayer, getMLAnomaliesTypicalLayer } from './map_config';
|
||||
import { MlEmbeddedMapComponent } from '../../components/ml_embedded_map';
|
||||
import { useMlKibana } from '../../contexts/kibana';
|
||||
interface Props {
|
||||
seriesConfig: Dictionary<any>;
|
||||
}
|
||||
|
||||
export function EmbeddedMapComponentWrapper({ seriesConfig }: Props) {
|
||||
const {
|
||||
services: { maps: mapsPlugin },
|
||||
} = useMlKibana();
|
||||
|
||||
const [layerList, setLayerList] = useState<LayerDescriptor[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -26,9 +31,16 @@ export function EmbeddedMapComponentWrapper({ seriesConfig }: Props) {
|
|||
}
|
||||
}, [seriesConfig]);
|
||||
|
||||
return (
|
||||
return mapsPlugin ? (
|
||||
<div data-test-subj="xpack.ml.explorer.embeddedMap" style={{ width: '100%', height: 300 }}>
|
||||
<MlEmbeddedMapComponent layerList={layerList} />
|
||||
<mapsPlugin.Map
|
||||
layerList={layerList}
|
||||
hideFilterActions={true}
|
||||
mapSettings={{
|
||||
initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS,
|
||||
autoFitToDataBounds: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
) : null;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import memoizeOne from 'memoize-one';
|
|||
import { isEqual } from 'lodash';
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { ES_GEO_FIELD_TYPE, LayerDescriptor } from '@kbn/maps-plugin/common';
|
||||
import type { MapsStartApi } from '@kbn/maps-plugin/public';
|
||||
import type { CreateLayerDescriptorParams, MapsStartApi } from '@kbn/maps-plugin/public';
|
||||
import type { Query } from '@kbn/es-query';
|
||||
import type { Field, SplitField } from '@kbn/ml-anomaly-utils';
|
||||
import { ChartLoader } from '../chart_loader';
|
||||
|
@ -30,7 +30,6 @@ export class MapLoader extends ChartLoader {
|
|||
geoField: Field,
|
||||
splitField: SplitField,
|
||||
fieldValues: string[],
|
||||
filters?: any[],
|
||||
savedSearchQuery?: Query
|
||||
) {
|
||||
const layerList: LayerDescriptor[] = [];
|
||||
|
@ -41,11 +40,10 @@ export class MapLoader extends ChartLoader {
|
|||
? `${splitField.name}:${fieldValues[0]} ${query ? `and ${query}` : ''}`
|
||||
: `${query ? query : ''}`;
|
||||
|
||||
const params: any = {
|
||||
const params: CreateLayerDescriptorParams = {
|
||||
indexPatternId: this._dataView.id,
|
||||
geoFieldName: geoField.name,
|
||||
geoFieldType: geoField.type as unknown as ES_GEO_FIELD_TYPE,
|
||||
filters: filters ?? [],
|
||||
query: { query: queryString, language: 'kuery' },
|
||||
};
|
||||
|
||||
|
|
|
@ -9,9 +9,10 @@ import type { FC } from 'react';
|
|||
import React from 'react';
|
||||
import { EuiFlexGrid, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import type { LayerDescriptor } from '@kbn/maps-plugin/common';
|
||||
import { INITIAL_LOCATION } from '@kbn/maps-plugin/common';
|
||||
import type { Aggregation, Field, SplitField } from '@kbn/ml-anomaly-utils';
|
||||
import { SplitCards, useAnimateSplit } from '../split_cards';
|
||||
import { MlEmbeddedMapComponent } from '../../../../../../../components/ml_embedded_map';
|
||||
import { useMlKibana } from '../../../../../../../contexts/kibana';
|
||||
import { JOB_TYPE } from '../../../../../../../../../common/constants/new_job';
|
||||
import { DetectorTitle } from '../detector_title';
|
||||
|
||||
|
@ -31,6 +32,10 @@ export const GeoMapExamples: FC<Props> = ({
|
|||
geoAgg,
|
||||
layerList,
|
||||
}) => {
|
||||
const {
|
||||
services: { maps: mapsPlugin },
|
||||
} = useMlKibana();
|
||||
|
||||
const animateSplit = useAnimateSplit();
|
||||
|
||||
return (
|
||||
|
@ -46,9 +51,18 @@ export const GeoMapExamples: FC<Props> = ({
|
|||
<>
|
||||
{geoAgg && geoField ? <DetectorTitle index={0} agg={geoAgg} field={geoField} /> : null}
|
||||
<EuiSpacer size="s" />
|
||||
<span data-test-subj="mlGeoJobWizardMap" style={{ width: '100%', height: 400 }}>
|
||||
<MlEmbeddedMapComponent layerList={layerList} />
|
||||
</span>
|
||||
{mapsPlugin && (
|
||||
<span data-test-subj="mlGeoJobWizardMap" style={{ width: '100%', height: 400 }}>
|
||||
<mapsPlugin.Map
|
||||
layerList={layerList}
|
||||
hideFilterActions={true}
|
||||
mapSettings={{
|
||||
initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS,
|
||||
autoFitToDataBounds: true,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
|
|
|
@ -29,7 +29,7 @@ export const GeoDetector: FC<Props> = ({ setIsValid }) => {
|
|||
const [layerList, setLayerList] = useState<LayerDescriptor[]>([]);
|
||||
|
||||
const {
|
||||
services: { data, notifications: toasts },
|
||||
services: { notifications: toasts },
|
||||
} = useMlKibana();
|
||||
const { mapLoader } = useContext(JobCreatorContext);
|
||||
|
||||
|
@ -72,14 +72,12 @@ export const GeoDetector: FC<Props> = ({ setIsValid }) => {
|
|||
useEffect(() => {
|
||||
async function getMapLayersForGeoJob() {
|
||||
if (jobCreator.geoField) {
|
||||
const { filter, query } = jobCreator.savedSearchQuery ?? {};
|
||||
const filters = [...data.query.filterManager.getFilters(), ...(filter ?? [])];
|
||||
const { query } = jobCreator.savedSearchQuery ?? {};
|
||||
|
||||
const layers = await mapLoader.getMapLayersForGeoJob(
|
||||
jobCreator.geoField,
|
||||
jobCreator.splitField,
|
||||
fieldValues,
|
||||
filters,
|
||||
query
|
||||
);
|
||||
setLayerList(layers);
|
||||
|
|
|
@ -24,7 +24,7 @@ export const GeoDetectorsSummary: FC = () => {
|
|||
const splitField = jobCreator.splitField;
|
||||
|
||||
const {
|
||||
services: { data, notifications },
|
||||
services: { notifications },
|
||||
} = useMlKibana();
|
||||
|
||||
// Load example field values for summary view
|
||||
|
@ -56,14 +56,12 @@ export const GeoDetectorsSummary: FC = () => {
|
|||
useEffect(() => {
|
||||
async function getMapLayersForGeoJob() {
|
||||
if (geoField) {
|
||||
const { filter, query } = jobCreator.savedSearchQuery ?? {};
|
||||
const filters = [...data.query.filterManager.getFilters(), ...(filter ?? [])];
|
||||
const { query } = jobCreator.savedSearchQuery ?? {};
|
||||
|
||||
const layers = await mapLoader.getMapLayersForGeoJob(
|
||||
geoField,
|
||||
splitField,
|
||||
fieldValues,
|
||||
filters,
|
||||
query
|
||||
);
|
||||
setLayerList(layers);
|
||||
|
|
|
@ -46,7 +46,7 @@ export function MachineLearningJobWizardGeoProvider({ getService }: FtrProviderC
|
|||
);
|
||||
|
||||
await testSubjects.existOrFail('mlGeoJobWizardMap');
|
||||
await testSubjects.existOrFail('mlEmbeddedMapContent');
|
||||
await testSubjects.existOrFail('mapContainer');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue