Replace legacy Maps embeddable with MapComponent in APM GeoMap (#182753)

## Summary

Replaces the Maps embeddable with a new MapComponent in the APM
solution.

The Maps plugin now provides an easier to use MapComponent for
consumers. The legacy MapEmbeddable factory is also being removed as
part of https://github.com/elastic/kibana/issues/174960 which requires
making changes to consumers.


### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Nick Peihl 2024-05-09 15:20:31 -04:00 committed by GitHub
parent e1ec2429ae
commit 99e7bd3ff5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 44 additions and 179 deletions

View file

@ -10,27 +10,15 @@ import { DataView } from '@kbn/data-views-plugin/common';
import React from 'react';
import { EmbeddedMap } from './embedded_map';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks';
import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context';
import { MemoryRouter } from 'react-router-dom';
import { MapTypes } from '../../../../../../common/mobile/constants';
describe('Embedded Map', () => {
it('it renders', async () => {
const mockSetLayerList = jest.fn();
const mockUpdateInput = jest.fn();
const mockRender = jest.fn();
const mockReload = jest.fn();
const mockEmbeddable = embeddablePluginMock.createStartContract();
mockEmbeddable.getEmbeddableFactory = jest.fn().mockImplementation(() => ({
create: () => ({
setLayerList: mockSetLayerList,
updateInput: mockUpdateInput,
render: mockRender,
reload: mockReload,
}),
}));
const mockMapsStartService = {
Map: jest.fn().mockImplementation(() => <div data-test-subj="mockMap" />),
};
const mockSpaces = {
getActiveSpace: jest.fn().mockImplementation(() => ({ id: 'mockSpaceId' })),
@ -52,7 +40,7 @@ describe('Embedded Map', () => {
initialEntries={['/mobile-services/{serviceName}/overview?rangeFrom=now-15m&rangeTo=now&']}
>
<MockApmPluginContextWrapper>
<KibanaContextProvider services={{ embeddable: mockEmbeddable, spaces: mockSpaces }}>
<KibanaContextProvider services={{ maps: mockMapsStartService, spaces: mockSpaces }}>
<EmbeddedMap
selectedMap={MapTypes.Http}
filters={[]}
@ -65,9 +53,6 @@ describe('Embedded Map', () => {
</MemoryRouter>
);
expect(await findByTestId('serviceOverviewEmbeddedMap')).toBeInTheDocument();
expect(mockSetLayerList).toHaveBeenCalledTimes(1);
expect(mockUpdateInput).toHaveBeenCalledTimes(1);
expect(mockRender).toHaveBeenCalledTimes(1);
expect(await findByTestId('mockMap')).toBeInTheDocument();
});
});

View file

@ -5,20 +5,15 @@
* 2.0.
*/
import React, { useEffect, useState, useRef } from 'react';
import React, { useMemo } from 'react';
import { DataView } from '@kbn/data-views-plugin/common';
import { v4 as uuidv4 } from 'uuid';
import { MapEmbeddable, MapEmbeddableInput, MapEmbeddableOutput } from '@kbn/maps-plugin/public';
import { MAP_SAVED_OBJECT_TYPE } from '@kbn/maps-plugin/common';
import { ErrorEmbeddable, ViewMode, isErrorEmbeddable } from '@kbn/embeddable-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import { EuiText } from '@elastic/eui';
import type { Filter } from '@kbn/es-query';
import { ApmPluginStartDeps } from '../../../../../plugin';
import { getLayerList } from './map_layers/get_layer_list';
import { MapTypes } from '../../../../../../common/mobile/constants';
function EmbeddedMapComponent({
selectedMap,
start,
@ -34,137 +29,43 @@ function EmbeddedMapComponent({
filters: Filter[];
dataView?: DataView;
}) {
const [error, setError] = useState<boolean>();
const { maps } = useKibana<ApmPluginStartDeps>().services;
const [embeddable, setEmbeddable] = useState<MapEmbeddable | ErrorEmbeddable | undefined>();
const embeddableRoot: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
const {
embeddable: embeddablePlugin,
maps,
notifications,
} = useKibana<ApmPluginStartDeps>().services;
useEffect(() => {
async function setupEmbeddable() {
const factory = embeddablePlugin?.getEmbeddableFactory<
MapEmbeddableInput,
MapEmbeddableOutput,
MapEmbeddable
>(MAP_SAVED_OBJECT_TYPE);
if (!factory) {
setError(true);
notifications?.toasts.addDanger({
title: i18n.translate('xpack.apm.serviceOverview.embeddedMap.error.toastTitle', {
defaultMessage: 'An error occurred when adding map embeddable',
}),
text: i18n.translate('xpack.apm.serviceOverview.embeddedMap.error.toastDescription', {
defaultMessage: `Embeddable factory with id "{embeddableFactoryId}" was not found.`,
values: {
embeddableFactoryId: MAP_SAVED_OBJECT_TYPE,
},
}),
});
return;
}
const input: MapEmbeddableInput = {
attributes: { title: '' },
id: uuidv4(),
title: i18n.translate('xpack.apm.serviceOverview.embeddedMap.input.title', {
defaultMessage: 'Latency by country',
}),
filters,
viewMode: ViewMode.VIEW,
mapCenter: { lat: 20.43425, lon: 0, zoom: 1.25 },
isLayerTOCOpen: false,
query: {
query: kuery,
language: 'kuery',
},
timeRange: {
from: start,
to: end,
},
hideFilterActions: true,
};
const embeddableObject = await factory.create(input);
setEmbeddable(embeddableObject);
}
setupEmbeddable();
// Set up exactly once after the component mounts
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// We can only render after embeddable has already initialized
useEffect(() => {
if (embeddableRoot.current && embeddable) {
embeddable.render(embeddableRoot.current);
}
}, [embeddable, embeddableRoot]);
useEffect(() => {
const setLayerList = async () => {
if (embeddable && !isErrorEmbeddable(embeddable) && dataView?.id) {
const layerList = await getLayerList({
const layerList = useMemo(() => {
return dataView?.id
? getLayerList({
selectedMap,
maps,
dataViewId: dataView.id,
});
await Promise.all([embeddable.setLayerList(layerList), embeddable.reload()]);
}
};
setLayerList();
}, [embeddable, selectedMap, maps, dataView]);
useEffect(() => {
if (embeddable) {
embeddable.updateInput({
filters,
query: {
query: kuery,
language: 'kuery',
},
timeRange: {
from: start,
to: end,
},
});
}
}, [start, end, kuery, filters, embeddable, selectedMap]);
dataViewId: dataView?.id,
})
: [];
}, [selectedMap, maps, dataView?.id]);
return (
<>
{error && (
<EuiText size="s">
<p>
{i18n.translate('xpack.apm.serviceOverview.embeddedMap.error', {
defaultMessage: 'Could not load map',
})}
</p>
</EuiText>
)}
{!error && (
<div
data-test-subj="serviceOverviewEmbeddedMap"
css={css`
width: 100%;
height: 500px;
display: flex;
flex: 1 1 100%;
z-index: 1;
min-height: 0;
`}
ref={embeddableRoot}
/>
)}
</>
<div
data-test-subj="serviceOverviewEmbeddedMap"
style={{ width: '100%', height: '500px', display: 'flex', flex: '1 1 100%', zIndex: 1 }}
>
{maps &&
maps.Map({
title: i18n.translate('xpack.apm.serviceOverview.embeddedMap.input.title', {
defaultMessage: 'Latency by country',
}),
filters,
query: {
query: kuery,
language: 'kuery',
},
timeRange: {
from: start,
to: end,
},
layerList,
hideFilterActions: true,
isLayerTOCOpen: false,
mapCenter: { lat: 20.43425, lon: 0, zoom: 1.25 },
})}
</div>
);
}

View file

@ -41,14 +41,12 @@ const label = i18n.translate('xpack.apm.serviceOverview.embeddedMap.httpRequests
defaultMessage: 'HTTP requests',
});
export async function getHttpRequestsLayerList(maps: MapsStartApi | undefined, dataViewId: string) {
export function getHttpRequestsLayerList(maps: MapsStartApi | undefined, dataViewId: string) {
const whereQuery = {
language: 'kuery',
query: `${PROCESSOR_EVENT}:${ProcessorEvent.span} and ${SPAN_SUBTYPE}:${MobileSpanSubtype.Http} and ${SPAN_TYPE}:${MobileSpanType.External}`,
};
const basemapLayerDescriptor = await maps?.createLayerDescriptors?.createBasemapLayerDescriptor();
const httpRequestsByCountryLayer: VectorLayerDescriptor = {
joins: [
{
@ -127,9 +125,5 @@ export async function getHttpRequestsLayerList(maps: MapsStartApi | undefined, d
type: LAYER_TYPE.GEOJSON_VECTOR,
};
return [
...(basemapLayerDescriptor ? [basemapLayerDescriptor] : []),
httpRequestsByRegionLayer,
httpRequestsByCountryLayer,
] as BaseLayerDescriptor[];
return [httpRequestsByRegionLayer, httpRequestsByCountryLayer] as BaseLayerDescriptor[];
}

View file

@ -18,7 +18,7 @@ export function getLayerList({
selectedMap: MapTypes;
maps: MapsStartApi | undefined;
dataViewId: string;
}): Promise<LayerDescriptor[]> {
}): LayerDescriptor[] {
switch (selectedMap) {
case MapTypes.Http:
return getHttpRequestsLayerList(maps, dataViewId);

View file

@ -36,9 +36,7 @@ const SESSION_PER_REGION = `__kbnjoin__cardinality_of_session.id__${PER_REGION_L
const label = i18n.translate('xpack.apm.serviceOverview.embeddedMap.session.metric.label', {
defaultMessage: 'Sessions',
});
export async function getSessionMapLayerList(maps: MapsStartApi | undefined, dataViewId: string) {
const basemapLayerDescriptor = await maps?.createLayerDescriptors?.createBasemapLayerDescriptor();
export function getSessionMapLayerList(maps: MapsStartApi | undefined, dataViewId: string) {
const sessionsByCountryLayer: VectorLayerDescriptor = {
joins: [
{
@ -117,9 +115,5 @@ export async function getSessionMapLayerList(maps: MapsStartApi | undefined, dat
type: LAYER_TYPE.GEOJSON_VECTOR,
};
return [
...(basemapLayerDescriptor ? [basemapLayerDescriptor] : []),
sessionsByRegionLayer,
sessionsByCountryLayer,
] as BaseLayerDescriptor[];
return [sessionsByRegionLayer, sessionsByCountryLayer] as BaseLayerDescriptor[];
}

View file

@ -8780,7 +8780,6 @@
"xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningText": "Nous n'avons pas pu déterminer à quelles JVM ces indicateurs correspondent. Cela provient probablement du fait que vous exécutez une version du serveur APM antérieure à 7.5. La mise à niveau du serveur APM vers la version 7.5 ou supérieure devrait résoudre le problème. Pour plus d'informations sur la mise à niveau, consultez {link}. Vous pouvez également utiliser la barre de recherche de Kibana pour filtrer par nom d'hôte, par ID de conteneur ou en fonction d'autres champs.",
"xpack.apm.serviceOveriew.errorsTableOccurrences": "{occurrences} occ.",
"xpack.apm.serviceOverview.crashTableOccurrences": "{occurrences} occ.",
"xpack.apm.serviceOverview.embeddedMap.error.toastDescription": "L'usine incorporable ayant l'ID \"{embeddableFactoryId}\" est introuvable.",
"xpack.apm.serviceOverview.embeddedMap.subtitle": "Carte affichant le nombre total de {currentMap} en fonction du pays et de la région",
"xpack.apm.serviceOverview.errorsTableOccurrences": "{occurrences} occ.",
"xpack.apm.serviceOverview.instancesTableTitle": "{count} le plus élevé {count, plural, one {instance} other {instances}}",
@ -9926,8 +9925,6 @@
"xpack.apm.serviceOverview.embeddedMap.dropdown.http.requests.subtitle": "HTTP définit un ensemble de méthodes de requête pour indiquer l'action souhaitée à effectuer pour une ressource donnée",
"xpack.apm.serviceOverview.embeddedMap.dropdown.sessions": "Sessions",
"xpack.apm.serviceOverview.embeddedMap.dropdown.sessions.subtitle": "Une session d'application commence lorsqu'un utilisateur démarre une application et se termine lorsque l'application est fermée.",
"xpack.apm.serviceOverview.embeddedMap.error": "Impossible de charger la carte",
"xpack.apm.serviceOverview.embeddedMap.error.toastTitle": "Une erreur s'est produite lors de l'ajout de l'incorporation de la carte",
"xpack.apm.serviceOverview.embeddedMap.httpRequests.country.label": "Requêtes HTTP par pays",
"xpack.apm.serviceOverview.embeddedMap.httpRequests.metric.label": "Requêtes HTTP",
"xpack.apm.serviceOverview.embeddedMap.httpRequests.region.label": "Requêtes HTTP par région",

View file

@ -8762,7 +8762,6 @@
"xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningText": "これらのメトリックが所属する JVM を特定できませんでした。7.5 よりも古い APM Server を実行していることが原因である可能性が高いです。この問題は APM Server 7.5 以降にアップグレードすることで解決されます。アップグレードに関する詳細は、{link} をご覧ください。代わりに Kibana クエリバーを使ってホスト名、コンテナー ID、またはその他フィールドでフィルタリングすることもできます。",
"xpack.apm.serviceOveriew.errorsTableOccurrences": "{occurrences}件",
"xpack.apm.serviceOverview.crashTableOccurrences": "{occurrences}件",
"xpack.apm.serviceOverview.embeddedMap.error.toastDescription": "id \"{embeddableFactoryId}\"の埋め込み可能ファクトリが見つかりました。",
"xpack.apm.serviceOverview.embeddedMap.subtitle": "国と地域別に基づく{currentMap}の総数を示した地図",
"xpack.apm.serviceOverview.errorsTableOccurrences": "{occurrences}件",
"xpack.apm.serviceOverview.instancesTableTitle": "上位{count} {count, plural, other {件のインスタンス}}",
@ -9906,8 +9905,6 @@
"xpack.apm.serviceOverview.embeddedMap.dropdown.http.requests.subtitle": "HTTPは、特定のリソースに対して実行させたい任意のアクションを指示する一連のリクエストメソッドを定義します。",
"xpack.apm.serviceOverview.embeddedMap.dropdown.sessions": "セッション",
"xpack.apm.serviceOverview.embeddedMap.dropdown.sessions.subtitle": "ユーザーがアプリケーションを起動すると、アプリケーションセッションが開始します。アプリケーションが終了すると、アプリケーションセッションも終了します。",
"xpack.apm.serviceOverview.embeddedMap.error": "マップを読み込めませんでした",
"xpack.apm.serviceOverview.embeddedMap.error.toastTitle": "埋め込み可能なマップの追加中にエラーが発生しました",
"xpack.apm.serviceOverview.embeddedMap.httpRequests.country.label": "国別HTTPリクエスト",
"xpack.apm.serviceOverview.embeddedMap.httpRequests.metric.label": "HTTPリクエスト",
"xpack.apm.serviceOverview.embeddedMap.httpRequests.region.label": "地域別HTTPリクエスト",

View file

@ -8783,7 +8783,6 @@
"xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningText": "无法识别这些指标属于哪些 JVM。这可能因为运行的 APM Server 版本低于 7.5。如果升级到 APM Server 7.5 或更高版本,应可解决此问题。有关升级的详细信息,请参阅 {link}。或者,也可以使用 Kibana 查询栏按主机名、容器 ID 或其他字段筛选。",
"xpack.apm.serviceOveriew.errorsTableOccurrences": "{occurrences} 次",
"xpack.apm.serviceOverview.crashTableOccurrences": "{occurrences} 次",
"xpack.apm.serviceOverview.embeddedMap.error.toastDescription": "未找到 ID 为“{embeddableFactoryId}”的可嵌入工厂。",
"xpack.apm.serviceOverview.embeddedMap.subtitle": "根据国家和区域显示 {currentMap} 总数的地图",
"xpack.apm.serviceOverview.errorsTableOccurrences": "{occurrences} 次",
"xpack.apm.serviceOverview.instancesTableTitle": "排名前 {count} {count, plural, other {实例}}",
@ -9929,8 +9928,6 @@
"xpack.apm.serviceOverview.embeddedMap.dropdown.http.requests.subtitle": "HTTP 定义一组请求方法,以指示要对给定资源执行的所需操作",
"xpack.apm.serviceOverview.embeddedMap.dropdown.sessions": "会话",
"xpack.apm.serviceOverview.embeddedMap.dropdown.sessions.subtitle": "用户启动应用程序时,应用程序会话即开始;应用程序退出时会话即结束。",
"xpack.apm.serviceOverview.embeddedMap.error": "无法加载地图",
"xpack.apm.serviceOverview.embeddedMap.error.toastTitle": "加载地图可嵌入对象时出错",
"xpack.apm.serviceOverview.embeddedMap.httpRequests.country.label": "按国家/地区的 HTTP 请求",
"xpack.apm.serviceOverview.embeddedMap.httpRequests.metric.label": "HTTP 请求",
"xpack.apm.serviceOverview.embeddedMap.httpRequests.region.label": "按区域的 HTTP 请求",