Skip to content

Commit da9ad2a

Browse files
authored
[ML] Add embedded map to geo_point fields for Data Visualizer (#88880)
1 parent 723dd32 commit da9ad2a

File tree

46 files changed

+842
-223
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+842
-223
lines changed

x-pack/plugins/maps/public/embeddable/map_embeddable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ import {
6868
MapEmbeddableInput,
6969
MapEmbeddableOutput,
7070
} from './types';
71-
export { MapEmbeddableInput };
71+
export { MapEmbeddableInput, MapEmbeddableOutput };
7272

7373
export class MapEmbeddable
7474
extends Embeddable<MapEmbeddableInput, MapEmbeddableOutput>

x-pack/plugins/ml/kibana.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"security",
2525
"spaces",
2626
"management",
27-
"licenseManagement"
27+
"licenseManagement",
28+
"maps"
2829
],
2930
"server": true,
3031
"ui": true,
@@ -35,7 +36,8 @@
3536
"dashboard",
3637
"savedObjects",
3738
"home",
38-
"spaces"
39+
"spaces",
40+
"maps"
3941
],
4042
"extraPublicDirs": [
4143
"common"

x-pack/plugins/ml/public/application/_index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
@import 'components/navigation_menu/index';
3131
@import 'components/rule_editor/index'; // SASSTODO: This file overwrites EUI directly
3232
@import 'components/stats_bar/index';
33+
@import 'components/ml_embedded_map/index';
3334

3435
// Hacks are last so they can overwrite anything above if needed
3536
@import 'hacks';

x-pack/plugins/ml/public/application/app.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ const App: FC<AppProps> = ({ coreStart, deps, appMountParams }) => {
7777
security: deps.security,
7878
licenseManagement: deps.licenseManagement,
7979
storage: localStorage,
80+
embeddable: deps.embeddable,
81+
maps: deps.maps,
8082
...coreStart,
8183
};
8284

@@ -118,6 +120,7 @@ export const renderApp = (
118120
http: coreStart.http,
119121
security: deps.security,
120122
urlGenerators: deps.share.urlGenerators,
123+
maps: deps.maps,
121124
});
122125

123126
appMountParams.onAppLeave((actions) => actions.default());
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@import 'ml_embedded_map';
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.mlEmbeddedMapContent {
2+
width: 100%;
3+
height: 100%;
4+
display: flex;
5+
flex: 1 1 100%;
6+
z-index: 1;
7+
min-height: 0; // Absolute must for Firefox to scroll contents
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export { MlEmbeddedMapComponent } from './ml_embedded_map';
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React, { useEffect, useRef, useState } from 'react';
8+
9+
import { htmlIdGenerator } from '@elastic/eui';
10+
import { LayerDescriptor } from '../../../../../maps/common/descriptor_types';
11+
import {
12+
MapEmbeddable,
13+
MapEmbeddableInput,
14+
MapEmbeddableOutput,
15+
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
16+
} from '../../../../../maps/public/embeddable';
17+
import { MAP_SAVED_OBJECT_TYPE, RenderTooltipContentParams } from '../../../../../maps/public';
18+
import {
19+
EmbeddableFactory,
20+
ErrorEmbeddable,
21+
isErrorEmbeddable,
22+
ViewMode,
23+
} from '../../../../../../../src/plugins/embeddable/public';
24+
import { useMlKibana } from '../../contexts/kibana';
25+
26+
export function MlEmbeddedMapComponent({
27+
layerList,
28+
mapEmbeddableInput,
29+
renderTooltipContent,
30+
}: {
31+
layerList: LayerDescriptor[];
32+
mapEmbeddableInput?: MapEmbeddableInput;
33+
renderTooltipContent?: (params: RenderTooltipContentParams) => JSX.Element;
34+
}) {
35+
const [embeddable, setEmbeddable] = useState<ErrorEmbeddable | MapEmbeddable | undefined>();
36+
37+
const embeddableRoot: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
38+
const baseLayers = useRef<LayerDescriptor[]>();
39+
40+
const {
41+
services: { embeddable: embeddablePlugin, maps: mapsPlugin },
42+
} = useMlKibana();
43+
44+
const factory:
45+
| EmbeddableFactory<MapEmbeddableInput, MapEmbeddableOutput, MapEmbeddable>
46+
| undefined = embeddablePlugin
47+
? embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE)
48+
: undefined;
49+
50+
// Update the layer list with updated geo points upon refresh
51+
useEffect(() => {
52+
async function updateIndexPatternSearchLayer() {
53+
if (
54+
embeddable &&
55+
!isErrorEmbeddable(embeddable) &&
56+
Array.isArray(layerList) &&
57+
Array.isArray(baseLayers.current)
58+
) {
59+
embeddable.setLayerList([...baseLayers.current, ...layerList]);
60+
}
61+
}
62+
updateIndexPatternSearchLayer();
63+
}, [embeddable, layerList]);
64+
65+
useEffect(() => {
66+
async function setupEmbeddable() {
67+
if (!factory) {
68+
// eslint-disable-next-line no-console
69+
console.error('Map embeddable not found.');
70+
return;
71+
}
72+
const input: MapEmbeddableInput = {
73+
id: htmlIdGenerator()(),
74+
attributes: { title: '' },
75+
filters: [],
76+
hidePanelTitles: true,
77+
refreshConfig: {
78+
value: 0,
79+
pause: false,
80+
},
81+
viewMode: ViewMode.VIEW,
82+
isLayerTOCOpen: false,
83+
hideFilterActions: true,
84+
// Zoom Lat/Lon values are set to make sure map is in center in the panel
85+
// It will also omit Greenland/Antarctica etc. NOTE: Can be removed when initialLocation is set
86+
mapCenter: {
87+
lon: 11,
88+
lat: 20,
89+
zoom: 1,
90+
},
91+
// can use mapSettings to center map on anomalies
92+
mapSettings: {
93+
disableInteractive: false,
94+
hideToolbarOverlay: false,
95+
hideLayerControl: false,
96+
hideViewControl: false,
97+
// Doesn't currently work with GEO_JSON. Will uncomment when https://github.com/elastic/kibana/pull/88294 is in
98+
// initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent
99+
autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query
100+
},
101+
};
102+
103+
const embeddableObject = await factory.create(input);
104+
105+
if (embeddableObject && !isErrorEmbeddable(embeddableObject)) {
106+
const basemapLayerDescriptor = mapsPlugin
107+
? await mapsPlugin.createLayerDescriptors.createBasemapLayerDescriptor()
108+
: null;
109+
110+
if (basemapLayerDescriptor) {
111+
baseLayers.current = [basemapLayerDescriptor];
112+
await embeddableObject.setLayerList(baseLayers.current);
113+
}
114+
}
115+
116+
setEmbeddable(embeddableObject);
117+
}
118+
119+
setupEmbeddable();
120+
// we want this effect to execute exactly once after the component mounts
121+
}, []);
122+
123+
useEffect(() => {
124+
if (embeddable && !isErrorEmbeddable(embeddable) && mapEmbeddableInput !== undefined) {
125+
embeddable.updateInput(mapEmbeddableInput);
126+
}
127+
}, [embeddable, mapEmbeddableInput]);
128+
129+
useEffect(() => {
130+
if (embeddable && !isErrorEmbeddable(embeddable) && renderTooltipContent !== undefined) {
131+
embeddable.setRenderTooltipContent(renderTooltipContent);
132+
}
133+
}, [embeddable, renderTooltipContent]);
134+
135+
// We can only render after embeddable has already initialized
136+
useEffect(() => {
137+
if (embeddableRoot.current && embeddable) {
138+
embeddable.render(embeddableRoot.current);
139+
}
140+
}, [embeddable, embeddableRoot]);
141+
142+
if (!embeddablePlugin) {
143+
// eslint-disable-next-line no-console
144+
console.error('Embeddable start plugin not found');
145+
return null;
146+
}
147+
if (!mapsPlugin) {
148+
// eslint-disable-next-line no-console
149+
console.error('Maps start plugin not found');
150+
return null;
151+
}
152+
153+
return (
154+
<div
155+
data-test-subj="mlEmbeddedMapContent"
156+
className="mlEmbeddedMapContent"
157+
ref={embeddableRoot}
158+
/>
159+
);
160+
}

x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@ import { LicenseManagementUIPluginSetup } from '../../../../../license_managemen
1515
import { SharePluginStart } from '../../../../../../../src/plugins/share/public';
1616
import { MlServicesContext } from '../../app';
1717
import { IStorageWrapper } from '../../../../../../../src/plugins/kibana_utils/public';
18+
import type { EmbeddableStart } from '../../../../../../../src/plugins/embeddable/public';
19+
import { MapsStartApi } from '../../../../../maps/public';
1820

1921
interface StartPlugins {
2022
data: DataPublicPluginStart;
2123
security?: SecurityPluginSetup;
2224
licenseManagement?: LicenseManagementUIPluginSetup;
2325
share: SharePluginStart;
26+
embeddable: EmbeddableStart;
27+
maps?: MapsStartApi;
2428
}
2529
export type StartServices = CoreStart &
2630
StartPlugins & {

x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import React from 'react';
88
import {
99
BooleanContent,
1010
DateContent,
11-
GeoPointContent,
1211
IpContent,
1312
KeywordContent,
1413
OtherContent,
1514
TextContent,
1615
NumberContent,
1716
} from '../../../stats_table/components/field_data_expanded_row';
17+
import { GeoPointContent } from './geo_point_content/geo_point_content';
1818
import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types';
1919
import type { FileBasedFieldVisConfig } from '../../../stats_table/types/field_vis_config';
2020

0 commit comments

Comments
 (0)