mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[embeddable refactor] decouple MapEmbeddable from Security Solution plugin (#182737)
Part of https://github.com/elastic/kibana/issues/182020 @elastic/kibana-presentation is refactoring the Embeddable framework. Part of this effort is decoupling plugins from the legacy Embeddable framework. The Maps plugin provides a react component from the plugins start contract that can be used instead of natively using MapEmbeddable. This PR swaps out the native MapEmbeddable implementation with the react component exposed from the Maps plugin. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
98329a8879
commit
1c15e9a677
10 changed files with 76 additions and 425 deletions
|
@ -21,6 +21,7 @@ import type { ILayer } from '../classes/layers/layer';
|
|||
|
||||
export type MapApi = HasType<'map'> & {
|
||||
getLayerList: () => ILayer[];
|
||||
reload: () => void;
|
||||
} & PublishesDataViews &
|
||||
PublishesPanelTitle &
|
||||
PublishesUnifiedSearch &
|
||||
|
|
|
@ -14,6 +14,7 @@ import type { LayerDescriptor, MapCenterAndZoom, MapSettings } from '../../commo
|
|||
import { MapEmbeddable } from './map_embeddable';
|
||||
import { createBasemapLayerDescriptor } from '../classes/layers/create_basemap_layer_descriptor';
|
||||
import { RenderToolTipContent } from '../classes/tooltips/tooltip_property';
|
||||
import { MapApi } from './map_api';
|
||||
|
||||
export interface Props {
|
||||
title?: string;
|
||||
|
@ -27,6 +28,7 @@ export interface Props {
|
|||
mapCenter?: MapCenterAndZoom;
|
||||
onInitialRenderComplete?: () => void;
|
||||
getTooltipRenderer?: () => RenderToolTipContent;
|
||||
onApiAvailable?: (api: MapApi) => void;
|
||||
/*
|
||||
* Set to false to exclude sharing attributes 'data-*'.
|
||||
*/
|
||||
|
@ -70,6 +72,9 @@ export class MapComponent extends Component<Props> {
|
|||
if (this.props.getTooltipRenderer) {
|
||||
this._mapEmbeddable.setRenderTooltipContent(this.props.getTooltipRenderer());
|
||||
}
|
||||
if (this.props.onApiAvailable) {
|
||||
this.props.onApiAvailable(this._mapEmbeddable as MapApi);
|
||||
}
|
||||
|
||||
if (this.props.onInitialRenderComplete) {
|
||||
this._mapEmbeddable
|
||||
|
|
|
@ -398,36 +398,9 @@ const mockApmDataStreamClientServerLineLayer = {
|
|||
label: 'traces-apm*,logs-apm*,metrics-apm*,apm-* | Line',
|
||||
};
|
||||
|
||||
export const mockLayerList = [
|
||||
{
|
||||
sourceDescriptor: { type: 'EMS_TMS', isAutoSelect: true },
|
||||
id: 'uuidv4()',
|
||||
label: null,
|
||||
minZoom: 0,
|
||||
maxZoom: 24,
|
||||
alpha: 1,
|
||||
visible: true,
|
||||
style: null,
|
||||
type: LAYER_TYPE.EMS_VECTOR_TILE,
|
||||
},
|
||||
mockLineLayer,
|
||||
mockDestinationLayer,
|
||||
mockSourceLayer,
|
||||
mockLayerGroup,
|
||||
];
|
||||
export const mockLayerList = [mockLineLayer, mockDestinationLayer, mockSourceLayer, mockLayerGroup];
|
||||
|
||||
export const mockLayerListDouble = [
|
||||
{
|
||||
sourceDescriptor: { type: 'EMS_TMS', isAutoSelect: true },
|
||||
id: 'uuidv4()',
|
||||
label: null,
|
||||
minZoom: 0,
|
||||
maxZoom: 24,
|
||||
alpha: 1,
|
||||
visible: true,
|
||||
style: null,
|
||||
type: LAYER_TYPE.EMS_VECTOR_TILE,
|
||||
},
|
||||
mockLineLayer,
|
||||
mockDestinationLayer,
|
||||
mockSourceLayer,
|
||||
|
@ -439,17 +412,6 @@ export const mockLayerListDouble = [
|
|||
];
|
||||
|
||||
export const mockLayerListMixed = [
|
||||
{
|
||||
sourceDescriptor: { type: 'EMS_TMS', isAutoSelect: true },
|
||||
id: 'uuidv4()',
|
||||
label: null,
|
||||
minZoom: 0,
|
||||
maxZoom: 24,
|
||||
alpha: 1,
|
||||
visible: true,
|
||||
style: null,
|
||||
type: LAYER_TYPE.EMS_VECTOR_TILE,
|
||||
},
|
||||
mockLineLayer,
|
||||
mockDestinationLayer,
|
||||
mockSourceLayer,
|
||||
|
|
|
@ -1,54 +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 { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks';
|
||||
import { createEmbeddable } from './create_embeddable';
|
||||
import { createHtmlPortalNode } from 'react-reverse-portal';
|
||||
|
||||
const mockEmbeddable = embeddablePluginMock.createStartContract();
|
||||
|
||||
mockEmbeddable.getEmbeddableFactory = jest.fn().mockImplementation(() => ({
|
||||
create: () => ({
|
||||
reload: jest.fn(),
|
||||
setRenderTooltipContent: jest.fn(),
|
||||
setLayerList: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('createEmbeddable', () => {
|
||||
test('attaches refresh action', async () => {
|
||||
const setQueryMock = jest.fn();
|
||||
await createEmbeddable(
|
||||
[],
|
||||
[],
|
||||
{ query: '', language: 'kuery' },
|
||||
'2020-07-07T08:20:18.966Z',
|
||||
'2020-07-08T08:20:18.966Z',
|
||||
setQueryMock,
|
||||
createHtmlPortalNode(),
|
||||
mockEmbeddable
|
||||
);
|
||||
expect(setQueryMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('attaches refresh action with correct reference', async () => {
|
||||
const setQueryMock = jest.fn(({ id, inspect, loading, refetch }) => refetch);
|
||||
const embeddable = await createEmbeddable(
|
||||
[],
|
||||
[],
|
||||
{ query: '', language: 'kuery' },
|
||||
'2020-07-07T08:20:18.966Z',
|
||||
'2020-07-08T08:20:18.966Z',
|
||||
setQueryMock,
|
||||
createHtmlPortalNode(),
|
||||
mockEmbeddable
|
||||
);
|
||||
expect(setQueryMock.mock.calls[0][0].refetch).not.toBe(embeddable.reload);
|
||||
setQueryMock.mock.results[0].value();
|
||||
expect(embeddable.reload).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -1,126 +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 { v4 as uuidv4 } from 'uuid';
|
||||
import React from 'react';
|
||||
import type { HtmlPortalNode } from 'react-reverse-portal';
|
||||
import { OutPortal } from 'react-reverse-portal';
|
||||
import type { Filter, Query } from '@kbn/es-query';
|
||||
import { MAP_SAVED_OBJECT_TYPE } from '@kbn/maps-plugin/public';
|
||||
import type {
|
||||
RenderTooltipContentParams,
|
||||
MapEmbeddable,
|
||||
MapEmbeddableInput,
|
||||
} from '@kbn/maps-plugin/public';
|
||||
import type {
|
||||
EmbeddableStart,
|
||||
EmbeddableOutput,
|
||||
ErrorEmbeddable,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import { isErrorEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import type { IndexPatternMapping } from './types';
|
||||
import { getLayerList } from './map_config';
|
||||
import * as i18n from './translations';
|
||||
|
||||
import type { GlobalTimeArgs } from '../../../../common/containers/use_global_time';
|
||||
|
||||
/**
|
||||
* Creates MapEmbeddable with provided initial configuration
|
||||
*
|
||||
* @param filters any existing global filters
|
||||
* @param indexPatterns list of index patterns to configure layers for
|
||||
* @param query initial query constraints as Query
|
||||
* @param startDate
|
||||
* @param endDate
|
||||
* @param setQuery function as provided by the GlobalTime component for reacting to refresh
|
||||
* @param portalNode wrapper for MapToolTip so it is not rendered in the embeddables component tree
|
||||
* @param embeddableApi
|
||||
*
|
||||
* @throws Error if EmbeddableFactory does not exist
|
||||
*/
|
||||
export const createEmbeddable = async (
|
||||
filters: Filter[],
|
||||
indexPatterns: IndexPatternMapping[],
|
||||
query: Query,
|
||||
startDate: GlobalTimeArgs['from'],
|
||||
endDate: GlobalTimeArgs['to'],
|
||||
setQuery: GlobalTimeArgs['setQuery'],
|
||||
portalNode: HtmlPortalNode,
|
||||
embeddableApi: EmbeddableStart
|
||||
): Promise<MapEmbeddable | ErrorEmbeddable> => {
|
||||
const factory = embeddableApi.getEmbeddableFactory<
|
||||
MapEmbeddableInput,
|
||||
EmbeddableOutput,
|
||||
MapEmbeddable
|
||||
>(MAP_SAVED_OBJECT_TYPE);
|
||||
|
||||
if (!factory) {
|
||||
throw new Error('Map embeddable factory undefined');
|
||||
}
|
||||
|
||||
const input: MapEmbeddableInput = {
|
||||
title: i18n.MAP_TITLE,
|
||||
attributes: { title: '' },
|
||||
id: uuidv4(),
|
||||
filters,
|
||||
hidePanelTitles: true,
|
||||
query,
|
||||
timeRange: {
|
||||
from: new Date(startDate).toISOString(),
|
||||
to: new Date(endDate).toISOString(),
|
||||
},
|
||||
viewMode: ViewMode.VIEW,
|
||||
isLayerTOCOpen: false,
|
||||
openTOCDetails: [],
|
||||
hideFilterActions: false,
|
||||
mapCenter: { lon: -1.05469, lat: 15.96133, zoom: 1 },
|
||||
disabledActions: ['CUSTOM_TIME_RANGE', 'CUSTOM_TIME_RANGE_BADGE'],
|
||||
};
|
||||
|
||||
const renderTooltipContent = ({
|
||||
addFilters,
|
||||
closeTooltip,
|
||||
features,
|
||||
isLocked,
|
||||
getLayerName,
|
||||
loadFeatureProperties,
|
||||
loadFeatureGeometry,
|
||||
}: RenderTooltipContentParams) => {
|
||||
const props = {
|
||||
addFilters,
|
||||
closeTooltip,
|
||||
features,
|
||||
isLocked,
|
||||
getLayerName,
|
||||
loadFeatureProperties,
|
||||
loadFeatureGeometry,
|
||||
};
|
||||
return <OutPortal node={portalNode} {...props} />;
|
||||
};
|
||||
|
||||
const embeddableObject = await factory.create(input);
|
||||
|
||||
if (!embeddableObject) {
|
||||
throw new Error('Map embeddable is undefined');
|
||||
}
|
||||
|
||||
if (!isErrorEmbeddable(embeddableObject)) {
|
||||
embeddableObject.setRenderTooltipContent(renderTooltipContent);
|
||||
// @ts-expect-error
|
||||
embeddableObject.setLayerList(getLayerList(indexPatterns));
|
||||
}
|
||||
|
||||
// Wire up to app refresh action
|
||||
setQuery({
|
||||
id: 'embeddedMap', // Scope to page type if using map elsewhere
|
||||
inspect: null,
|
||||
loading: false,
|
||||
refetch: () => embeddableObject.reload(),
|
||||
});
|
||||
|
||||
return embeddableObject;
|
||||
};
|
|
@ -10,14 +10,11 @@ import React from 'react';
|
|||
import { TestProviders, mockGlobalState, createMockStore } from '../../../../common/mock';
|
||||
|
||||
import { EmbeddedMapComponent } from './embedded_map';
|
||||
import { createEmbeddable } from './create_embeddable';
|
||||
import { getLayerList } from './map_config';
|
||||
import { useIsFieldInIndexPattern } from '../../../containers/fields';
|
||||
import { buildTimeRangeFilter } from '../../../../detections/components/alerts_table/helpers';
|
||||
|
||||
import { setStubKibanaServices } from '@kbn/embeddable-plugin/public/mocks';
|
||||
|
||||
jest.mock('./create_embeddable');
|
||||
jest.mock('./map_config');
|
||||
jest.mock('../../../../common/containers/sourcerer');
|
||||
jest.mock('../../../containers/fields');
|
||||
|
@ -33,6 +30,9 @@ jest.mock('../../../../common/lib/kibana', () => ({
|
|||
siem: { networkMap: '' },
|
||||
},
|
||||
},
|
||||
maps: {
|
||||
Map: () => <div data-test-subj="MapPanel">{'mockMap'}</div>,
|
||||
},
|
||||
storage: {
|
||||
get: mockGetStorage,
|
||||
set: mockSetStorage,
|
||||
|
@ -46,12 +46,7 @@ jest.mock('../../../../common/lib/kibana', () => ({
|
|||
remove: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
jest.mock('@kbn/embeddable-plugin/public', () => ({
|
||||
...jest.requireActual('@kbn/embeddable-plugin/public'),
|
||||
EmbeddablePanel: jest.fn().mockReturnValue(<div data-test-subj="EmbeddablePanel" />),
|
||||
}));
|
||||
|
||||
const mockCreateEmbeddable = createEmbeddable as jest.Mock;
|
||||
const mockUseIsFieldInIndexPattern = useIsFieldInIndexPattern as jest.Mock;
|
||||
const mockGetStorage = jest.fn();
|
||||
const mockSetStorage = jest.fn();
|
||||
|
@ -92,35 +87,6 @@ const mockState = {
|
|||
},
|
||||
};
|
||||
const defaultMockStore = createMockStore(mockState);
|
||||
const mockUpdateInput = jest.fn();
|
||||
const embeddableValue = {
|
||||
destroyed: false,
|
||||
enhancements: { dynamicActions: {} },
|
||||
getActionContext: jest.fn(),
|
||||
getFilterActions: jest.fn(),
|
||||
id: '70969ddc-4d01-4048-8073-4ea63d595638',
|
||||
input: {
|
||||
viewMode: 'view',
|
||||
title: 'Source -> Destination Point-to-Point Map',
|
||||
id: '70969ddc-4d01-4048-8073-4ea63d595638',
|
||||
filters: Array(0),
|
||||
hidePanelTitles: true,
|
||||
},
|
||||
input$: {},
|
||||
isContainer: false,
|
||||
output: {},
|
||||
output$: {},
|
||||
parent: undefined,
|
||||
parentSubscription: undefined,
|
||||
renderComplete: {},
|
||||
runtimeId: 1,
|
||||
reload: jest.fn(),
|
||||
setLayerList: jest.fn(),
|
||||
setEventHandlers: jest.fn(),
|
||||
setRenderTooltipContent: jest.fn(),
|
||||
type: 'map',
|
||||
updateInput: mockUpdateInput,
|
||||
};
|
||||
const testProps = {
|
||||
endDate: '2019-08-28T05:50:57.877Z',
|
||||
filters: [],
|
||||
|
@ -132,7 +98,6 @@ describe('EmbeddedMapComponent', () => {
|
|||
beforeEach(() => {
|
||||
setQuery.mockClear();
|
||||
mockGetStorage.mockReturnValue(true);
|
||||
mockCreateEmbeddable.mockResolvedValue(embeddableValue);
|
||||
mockUseIsFieldInIndexPattern.mockReturnValue(() => true);
|
||||
|
||||
// stub Kibana services for the embeddable plugin to ensure embeddable panel renders.
|
||||
|
@ -154,21 +119,7 @@ describe('EmbeddedMapComponent', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('calls updateInput with time range filter', async () => {
|
||||
render(
|
||||
<TestProviders store={defaultMockStore}>
|
||||
<EmbeddedMapComponent {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateInput).toHaveBeenCalledTimes(2);
|
||||
expect(mockUpdateInput).toHaveBeenNthCalledWith(2, {
|
||||
filters: buildTimeRangeFilter(testProps.startDate, testProps.endDate),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('renders EmbeddablePanel from embeddable plugin', async () => {
|
||||
test('renders Map', async () => {
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders store={defaultMockStore}>
|
||||
<EmbeddedMapComponent {...testProps} />
|
||||
|
@ -176,9 +127,8 @@ describe('EmbeddedMapComponent', () => {
|
|||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('EmbeddablePanel')).toBeInTheDocument();
|
||||
expect(getByTestId('MapPanel')).toBeInTheDocument();
|
||||
expect(queryByTestId('IndexPatternsMissingPrompt')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('loading-spinner')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -198,24 +148,8 @@ describe('EmbeddedMapComponent', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(queryByTestId('EmbeddablePanel')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('MapPanel')).not.toBeInTheDocument();
|
||||
expect(getByTestId('IndexPatternsMissingPrompt')).toBeInTheDocument();
|
||||
expect(queryByTestId('loading-spinner')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders Loader', async () => {
|
||||
mockCreateEmbeddable.mockResolvedValue(null);
|
||||
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders store={defaultMockStore}>
|
||||
<EmbeddedMapComponent {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(queryByTestId('EmbeddablePanel')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('IndexPatternsMissingPrompt')).not.toBeInTheDocument();
|
||||
expect(getByTestId('loading-spinner')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -10,23 +10,17 @@
|
|||
import { EuiAccordion, EuiLink, EuiText } from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createHtmlPortalNode, InPortal } from 'react-reverse-portal';
|
||||
import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal';
|
||||
import styled, { css } from 'styled-components';
|
||||
import type { Filter, Query } from '@kbn/es-query';
|
||||
import {
|
||||
EmbeddablePanel,
|
||||
isErrorEmbeddable,
|
||||
type ErrorEmbeddable,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import type { MapEmbeddable } from '@kbn/maps-plugin/public/embeddable';
|
||||
import { isEqual } from 'lodash/fp';
|
||||
import type { MapApi, RenderTooltipContentParams } from '@kbn/maps-plugin/public';
|
||||
import type { LayerDescriptor } from '@kbn/maps-plugin/common';
|
||||
import { buildTimeRangeFilter } from '../../../../detections/components/alerts_table/helpers';
|
||||
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
|
||||
import { useIsFieldInIndexPattern } from '../../../containers/fields';
|
||||
import { Loader } from '../../../../common/components/loader';
|
||||
import type { GlobalTimeArgs } from '../../../../common/containers/use_global_time';
|
||||
import { Embeddable } from './embeddable';
|
||||
import { createEmbeddable } from './create_embeddable';
|
||||
import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt';
|
||||
import { MapToolTip } from './map_tool_tip/map_tool_tip';
|
||||
import * as i18n from './translations';
|
||||
|
@ -43,7 +37,7 @@ interface EmbeddableMapProps {
|
|||
maintainRatio?: boolean;
|
||||
}
|
||||
|
||||
const EmbeddableMap = styled.div.attrs(() => ({
|
||||
const EmbeddableMapRatioHolder = styled.div.attrs(() => ({
|
||||
className: 'siemEmbeddable__map',
|
||||
}))<EmbeddableMapProps>`
|
||||
.embPanel {
|
||||
|
@ -89,7 +83,18 @@ const StyledEuiAccordion = styled(EuiAccordion)`
|
|||
}
|
||||
`;
|
||||
|
||||
EmbeddableMap.displayName = 'EmbeddableMap';
|
||||
EmbeddableMapRatioHolder.displayName = 'EmbeddableMapRatioHolder';
|
||||
|
||||
const EmbeddableMapWrapper = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const EmbeddableMap = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
`;
|
||||
|
||||
export interface EmbeddedMapProps {
|
||||
query: Query;
|
||||
|
@ -106,10 +111,6 @@ export const EmbeddedMapComponent = ({
|
|||
setQuery,
|
||||
startDate,
|
||||
}: EmbeddedMapProps) => {
|
||||
const [embeddable, setEmbeddable] = React.useState<MapEmbeddable | undefined | ErrorEmbeddable>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const { services } = useKibana();
|
||||
const { storage } = services;
|
||||
|
||||
|
@ -126,7 +127,7 @@ export const EmbeddedMapComponent = ({
|
|||
|
||||
const isFieldInIndexPattern = useIsFieldInIndexPattern();
|
||||
|
||||
const [mapDataViews, setMapDataViews] = useState<SourcererDataView[]>([]);
|
||||
const [layerList, setLayerList] = useState<LayerDescriptor[]>([]);
|
||||
const [availableDataViews, setAvailableDataViews] = useState<SourcererDataView[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -140,11 +141,11 @@ export const EmbeddedMapComponent = ({
|
|||
// ensures only index patterns with maps fields are passed
|
||||
const goodDataViews = availableDataViews.filter((_, i) => apiResponse[i] ?? false);
|
||||
if (!canceled) {
|
||||
setMapDataViews(goodDataViews);
|
||||
setLayerList(getLayerList(goodDataViews));
|
||||
}
|
||||
} catch (e) {
|
||||
if (!canceled) {
|
||||
setMapDataViews([]);
|
||||
setLayerList([]);
|
||||
addError(e, { title: i18n.ERROR_CREATING_EMBEDDABLE });
|
||||
setIsError(true);
|
||||
}
|
||||
|
@ -174,88 +175,9 @@ export const EmbeddedMapComponent = ({
|
|||
// Search InPortal/OutPortal for implementation touch points
|
||||
const portalNode = React.useMemo(() => createHtmlPortalNode(), []);
|
||||
|
||||
// Initial Load useEffect
|
||||
useEffect(() => {
|
||||
let isSubscribed = true;
|
||||
async function setupEmbeddable() {
|
||||
// Create & set Embeddable
|
||||
try {
|
||||
const embeddableObject = await createEmbeddable(
|
||||
filters,
|
||||
mapDataViews,
|
||||
query,
|
||||
startDate,
|
||||
endDate,
|
||||
setQuery,
|
||||
portalNode,
|
||||
services.embeddable
|
||||
);
|
||||
if (isSubscribed) {
|
||||
setEmbeddable(embeddableObject);
|
||||
}
|
||||
} catch (e) {
|
||||
if (isSubscribed) {
|
||||
addError(e, { title: i18n.ERROR_CREATING_EMBEDDABLE });
|
||||
setIsError(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (embeddable == null && selectedPatterns.length > 0 && !isIndexError) {
|
||||
setupEmbeddable();
|
||||
}
|
||||
|
||||
return () => {
|
||||
isSubscribed = false;
|
||||
};
|
||||
}, [
|
||||
addError,
|
||||
endDate,
|
||||
embeddable,
|
||||
filters,
|
||||
mapDataViews,
|
||||
query,
|
||||
portalNode,
|
||||
services.embeddable,
|
||||
selectedPatterns,
|
||||
setQuery,
|
||||
startDate,
|
||||
isIndexError,
|
||||
]);
|
||||
|
||||
// update layer with new index patterns
|
||||
useEffect(() => {
|
||||
const setLayerList = async () => {
|
||||
if (embeddable != null && mapDataViews.length) {
|
||||
// @ts-expect-error
|
||||
await embeddable.setLayerList(getLayerList(mapDataViews));
|
||||
embeddable.reload();
|
||||
}
|
||||
};
|
||||
if (embeddable != null && !isErrorEmbeddable(embeddable)) {
|
||||
setLayerList();
|
||||
}
|
||||
}, [embeddable, mapDataViews]);
|
||||
|
||||
// queryExpression updated useEffect
|
||||
useEffect(() => {
|
||||
if (embeddable != null) {
|
||||
embeddable.updateInput({ query });
|
||||
}
|
||||
}, [embeddable, query]);
|
||||
|
||||
const timeRangeFilter = useMemo(
|
||||
() => buildTimeRangeFilter(startDate, endDate),
|
||||
[startDate, endDate]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (embeddable != null) {
|
||||
// pass time range as filter instead of via timeRange param
|
||||
// if user's data view does not have a time field, the timeRange param is not applied
|
||||
// using filter will always apply the time range
|
||||
embeddable.updateInput({ filters: [...filters, ...timeRangeFilter] });
|
||||
}
|
||||
}, [embeddable, filters, timeRangeFilter]);
|
||||
const appliedFilters = useMemo(() => {
|
||||
return [...filters, ...buildTimeRangeFilter(startDate, endDate)];
|
||||
}, [filters, startDate, endDate]);
|
||||
|
||||
const setDefaultMapVisibility = useCallback(
|
||||
(isOpen: boolean) => {
|
||||
|
@ -265,28 +187,40 @@ export const EmbeddedMapComponent = ({
|
|||
[storage]
|
||||
);
|
||||
|
||||
const content = useMemo(() => {
|
||||
if (!storageValue) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Embeddable>
|
||||
<InPortal node={portalNode}>
|
||||
<MapToolTip />
|
||||
</InPortal>
|
||||
|
||||
<EmbeddableMap maintainRatio={!isIndexError}>
|
||||
{isIndexError ? (
|
||||
<IndexPatternsMissingPrompt data-test-subj="missing-prompt" />
|
||||
) : embeddable != null ? (
|
||||
<EmbeddablePanel embeddable={embeddable} />
|
||||
) : (
|
||||
<Loader data-test-subj="loading-panel" overlay size="xl" />
|
||||
)}
|
||||
</EmbeddableMap>
|
||||
</Embeddable>
|
||||
);
|
||||
}, [embeddable, isIndexError, portalNode, storageValue]);
|
||||
const content = !storageValue ? null : (
|
||||
<Embeddable>
|
||||
<InPortal node={portalNode}>
|
||||
<MapToolTip />
|
||||
</InPortal>
|
||||
<EmbeddableMapWrapper>
|
||||
<EmbeddableMapRatioHolder maintainRatio={!isIndexError} />
|
||||
{isIndexError ? (
|
||||
<IndexPatternsMissingPrompt data-test-subj="missing-prompt" />
|
||||
) : (
|
||||
<EmbeddableMap>
|
||||
<services.maps.Map
|
||||
// eslint-disable-next-line react/display-name
|
||||
getTooltipRenderer={() => (tooltipProps: RenderTooltipContentParams) =>
|
||||
<OutPortal node={portalNode} {...tooltipProps} />}
|
||||
mapCenter={{ lon: -1.05469, lat: 15.96133, zoom: 1 }}
|
||||
layerList={layerList}
|
||||
filters={appliedFilters}
|
||||
query={query}
|
||||
onApiAvailable={(api: MapApi) => {
|
||||
// Wire up to app refresh action
|
||||
setQuery({
|
||||
id: 'embeddedMap', // Scope to page type if using map elsewhere
|
||||
inspect: null,
|
||||
loading: false,
|
||||
refetch: () => api.reload(),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EmbeddableMap>
|
||||
)}
|
||||
</EmbeddableMapWrapper>
|
||||
</Embeddable>
|
||||
);
|
||||
|
||||
return isError ? null : (
|
||||
<StyledEuiAccordion
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { euiPaletteColorBlind } from '@elastic/eui';
|
||||
import type { LayerDescriptor } from '@kbn/maps-plugin/common';
|
||||
import { LAYER_TYPE, SCALING_TYPES, SOURCE_TYPES } from '@kbn/maps-plugin/common';
|
||||
import type {
|
||||
IndexPatternMapping,
|
||||
|
@ -115,17 +116,6 @@ export const getRequiredMapsFields = (title: string): string[] => {
|
|||
*/
|
||||
export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => {
|
||||
return [
|
||||
{
|
||||
sourceDescriptor: { type: SOURCE_TYPES.EMS_TMS, isAutoSelect: true },
|
||||
id: uuidv4(),
|
||||
label: null,
|
||||
minZoom: 0,
|
||||
maxZoom: 24,
|
||||
alpha: 1,
|
||||
visible: true,
|
||||
style: null,
|
||||
type: LAYER_TYPE.EMS_VECTOR_TILE,
|
||||
},
|
||||
...indexPatternIds.reduce((acc: object[], { title, id }) => {
|
||||
const layerGroupDescriptor = {
|
||||
id: uuidv4(),
|
||||
|
@ -152,7 +142,7 @@ export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => {
|
|||
layerGroupDescriptor,
|
||||
];
|
||||
}, []),
|
||||
];
|
||||
] as LayerDescriptor[];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -93,6 +93,9 @@ jest.mock('../../../common/lib/kibana', () => {
|
|||
cases: {
|
||||
...mockCasesContract(),
|
||||
},
|
||||
maps: {
|
||||
Map: () => <div data-test-subj="MapPanel">{'mockMap'}</div>,
|
||||
},
|
||||
},
|
||||
}),
|
||||
useToasts: jest.fn().mockReturnValue({
|
||||
|
|
|
@ -59,6 +59,7 @@ import type { UpsellingService } from '@kbn/security-solution-upselling/service'
|
|||
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
|
||||
import type { PluginStartContract } from '@kbn/alerting-plugin/public/plugin';
|
||||
import type { MapsStartApi } from '@kbn/maps-plugin/public';
|
||||
import type { ResolverPluginSetup } from './resolver/types';
|
||||
import type { Inspect } from '../common/search_strategy';
|
||||
import type { Detections } from './detections';
|
||||
|
@ -130,6 +131,7 @@ export interface StartPlugins {
|
|||
timelines: TimelinesUIStart;
|
||||
sessionView: SessionViewStart;
|
||||
uiActions: UiActionsStart;
|
||||
maps: MapsStartApi;
|
||||
ml?: MlPluginStart;
|
||||
spaces?: SpacesPluginStart;
|
||||
dataViewFieldEditor: IndexPatternFieldEditorStart;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue