diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/__mocks__/mock.ts b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/__mocks__/mock.ts index 02889acb04866..77badaa1c09e6 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/__mocks__/mock.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/__mocks__/mock.ts @@ -8,6 +8,7 @@ import type { IndexPatternMapping } from '../types'; import type { IndexPatternSavedObject } from '../../../../../common/hooks/types'; import { LAYER_TYPE } from '@kbn/maps-plugin/common'; +import type { EuiThemeComputed } from '@elastic/eui'; export const mockIndexPatternIds: IndexPatternMapping[] = [ { title: 'filebeat-*', id: '8c7323ac-97ad-4b53-ac0a-40f8f691a918' }, @@ -50,11 +51,11 @@ export const mockSourceLayer = { properties: { fillColor: { type: 'STATIC', - options: { color: '#6092C0' }, + options: { color: 'euiColorVis4' }, }, lineColor: { type: 'STATIC', - options: { color: '#FFFFFF' }, + options: { color: 'euiColorVisNeutral0' }, }, lineWidth: { type: 'STATIC', options: { size: 2 } }, iconSize: { type: 'STATIC', options: { size: 8 } }, @@ -108,11 +109,11 @@ export const mockDestinationLayer = { properties: { fillColor: { type: 'STATIC', - options: { color: '#D36086' }, + options: { color: 'euiColorVis2' }, }, lineColor: { type: 'STATIC', - options: { color: '#FFFFFF' }, + options: { color: 'euiColorVisNeutral0' }, }, lineWidth: { type: 'STATIC', options: { size: 2 } }, iconSize: { type: 'STATIC', options: { size: 8 } }, @@ -164,11 +165,11 @@ export const mockClientLayer = { properties: { fillColor: { type: 'STATIC', - options: { color: '#6092C0' }, + options: { color: 'euiColorVis4' }, }, lineColor: { type: 'STATIC', - options: { color: '#FFFFFF' }, + options: { color: 'euiColorVisNeutral0' }, }, lineWidth: { type: 'STATIC', options: { size: 2 } }, iconSize: { type: 'STATIC', options: { size: 8 } }, @@ -227,11 +228,11 @@ export const mockServerLayer = { properties: { fillColor: { type: 'STATIC', - options: { color: '#D36086' }, + options: { color: 'euiColorVis2' }, }, lineColor: { type: 'STATIC', - options: { color: '#FFFFFF' }, + options: { color: 'euiColorVisNeutral0' }, }, lineWidth: { type: 'STATIC', options: { size: 2 } }, iconSize: { type: 'STATIC', options: { size: 8 } }, @@ -282,7 +283,7 @@ export const mockLineLayer = { properties: { fillColor: { type: 'STATIC', - options: { color: '#1EA593' }, + options: { color: '#6092C0' }, }, lineColor: { type: 'STATIC', @@ -347,7 +348,7 @@ export const mockClientServerLineLayer = { properties: { fillColor: { type: 'STATIC', - options: { color: '#1EA593' }, + options: { color: '#6092C0' }, }, lineColor: { type: 'STATIC', @@ -530,3 +531,13 @@ export const mockCommaFilebeatExclusionGlobIndexPattern: IndexPatternSavedObject title: 'filebeat-*,-filebeat-7.6.0*', }, }; + +export const mockEuiTheme: EuiThemeComputed<{}> = { + colors: { + vis: { + euiColorVisNeutral0: 'euiColorVisNeutral0', + euiColorVis4: 'euiColorVis4', + euiColorVis2: 'euiColorVis2', + }, + }, +} as unknown as EuiThemeComputed<{}>; diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/embedded_map.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/embedded_map.test.tsx index 574457d04f530..843378179f7f4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/embedded_map.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/embedded_map.test.tsx @@ -201,7 +201,7 @@ describe('EmbeddedMapComponent', () => { ); await waitFor(() => { - const dataViewArg = (getLayerList as jest.Mock).mock.calls[0][0]; + const dataViewArg = (getLayerList as jest.Mock).mock.calls[0][1]; expect(dataViewArg).toEqual([filebeatDataView]); }); }); @@ -221,7 +221,7 @@ describe('EmbeddedMapComponent', () => { ); await waitFor(() => { - const dataViewArg = (getLayerList as jest.Mock).mock.calls[0][0]; + const dataViewArg = (getLayerList as jest.Mock).mock.calls[0][1]; expect(dataViewArg).toEqual([filebeatDataView]); }); rerender( @@ -231,7 +231,7 @@ describe('EmbeddedMapComponent', () => { ); await waitFor(() => { // data view is updated with the returned embeddable.setLayerList callback, which is passesd getLayerList(dataViews) - const dataViewArg = (getLayerList as jest.Mock).mock.calls[1][0]; + const dataViewArg = (getLayerList as jest.Mock).mock.calls[1][1]; expect(dataViewArg).toEqual([filebeatDataView, packetbeatDataView]); }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/embedded_map.tsx b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/embedded_map.tsx index 6aa55916a40fd..9419b7d4e7d90 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/embedded_map.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/embedded_map.tsx @@ -7,7 +7,7 @@ // embedded map v2 -import { EuiAccordion, EuiLink, EuiText } from '@elastic/eui'; +import { EuiAccordion, EuiLink, EuiText, useEuiTheme } from '@elastic/eui'; import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; @@ -103,6 +103,7 @@ export const EmbeddedMapComponent = ({ setQuery, startDate, }: EmbeddedMapProps) => { + const { euiTheme } = useEuiTheme(); const { services } = useKibana(); const { storage } = services; @@ -133,7 +134,7 @@ export const EmbeddedMapComponent = ({ // ensures only index patterns with maps fields are passed const goodDataViews = availableDataViews.filter((_, i) => apiResponse[i] ?? false); if (!canceled) { - setLayerList(getLayerList(goodDataViews)); + setLayerList(getLayerList({ euiTheme }, goodDataViews)); } } catch (e) { if (!canceled) { @@ -149,7 +150,7 @@ export const EmbeddedMapComponent = ({ return () => { canceled = true; }; - }, [addError, availableDataViews, isFieldInIndexPattern]); + }, [addError, availableDataViews, euiTheme, isFieldInIndexPattern]); useEffect(() => { const dataViews = kibanaDataViews.filter((dataView) => diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/map_config.test.ts b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/map_config.test.ts index 0dc111f44801b..1c23a288066fa 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/map_config.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/map_config.test.ts @@ -19,6 +19,7 @@ import { mockLineLayer, mockServerLayer, mockSourceLayer, + mockEuiTheme, } from './__mocks__/mock'; jest.mock('uuid', () => { @@ -28,20 +29,28 @@ jest.mock('uuid', () => { }; }); +const layerProviderDependencies = { euiTheme: mockEuiTheme }; + describe('map_config', () => { describe('#getLayerList', () => { test('it returns the complete layerList with a source, destination, and line layer', () => { - const layerList = getLayerList(mockIndexPatternIds); + const layerList = getLayerList(layerProviderDependencies, mockIndexPatternIds); expect(layerList).toStrictEqual(mockLayerList); }); test('it returns the complete layerList for multiple indices', () => { - const layerList = getLayerList([...mockIndexPatternIds, ...mockIndexPatternIds]); + const layerList = getLayerList(layerProviderDependencies, [ + ...mockIndexPatternIds, + ...mockIndexPatternIds, + ]); expect(layerList).toStrictEqual(mockLayerListDouble); }); test('it returns the complete layerList for multiple indices with custom layer mapping', () => { - const layerList = getLayerList([...mockIndexPatternIds, ...mockAPMIndexPatternIds]); + const layerList = getLayerList(layerProviderDependencies, [ + ...mockIndexPatternIds, + ...mockAPMIndexPatternIds, + ]); expect(layerList).toStrictEqual(mockLayerListMixed); }); }); @@ -49,6 +58,7 @@ describe('map_config', () => { describe('#getSourceLayer', () => { test('it returns a source layer', () => { const layerList = getSourceLayer( + layerProviderDependencies, mockIndexPatternIds[0].title, mockIndexPatternIds[0].id, mockLayerGroup.id, @@ -59,6 +69,7 @@ describe('map_config', () => { test('it returns a source layer for custom layer mapping', () => { const layerList = getSourceLayer( + layerProviderDependencies, mockAPMIndexPatternIds[0].title, mockAPMIndexPatternIds[0].id, mockLayerGroup.id, @@ -71,6 +82,7 @@ describe('map_config', () => { describe('#getDestinationLayer', () => { test('it returns a destination layer', () => { const layerList = getDestinationLayer( + layerProviderDependencies, mockIndexPatternIds[0].title, mockIndexPatternIds[0].id, mockLayerGroup.id, @@ -81,6 +93,7 @@ describe('map_config', () => { test('it returns a destination layer for custom layer mapping', () => { const layerList = getDestinationLayer( + layerProviderDependencies, mockAPMIndexPatternIds[0].title, mockAPMIndexPatternIds[0].id, mockLayerGroup.id, diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/map_config.ts b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/map_config.ts index 19bf92ee761c7..93abbc69cf3a7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/map_config.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/embeddables/map_config.ts @@ -6,7 +6,7 @@ */ import { v4 as uuidv4 } from 'uuid'; -import { euiPaletteColorBlind } from '@elastic/eui'; +import { type EuiThemeComputed, 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 { @@ -16,7 +16,6 @@ import type { LayerMappingDetails, } from './types'; import * as i18n from './translations'; -const euiVisColorPalette = euiPaletteColorBlind(); // Update field mappings to modify what fields will be returned to map tooltip const sourceFieldMappings: Record = { @@ -108,13 +107,21 @@ export const getRequiredMapsFields = (title: string): string[] => { ]; }; +export interface LayerProviderDependencies { + euiTheme: EuiThemeComputed; +} + /** * Returns `Source/Destination Point-to-point` Map LayerList configuration, with a source, * destination, and line layer for each of the provided indexPatterns * + * @param dependencies dependencies, such as theme, necessary to configure layers * @param indexPatternIds array of indexPatterns' title and id */ -export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => { +export const getLayerList = ( + dependencies: LayerProviderDependencies, + indexPatternIds: IndexPatternMapping[] +) => { return [ ...indexPatternIds.reduce((acc: object[], { title, id }) => { const layerGroupDescriptor = { @@ -128,12 +135,14 @@ export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => { ...acc, getLineLayer(title, id, layerGroupDescriptor.id, lmc[title] ?? lmc.default), getDestinationLayer( + dependencies, title, id, layerGroupDescriptor.id, lmc[title]?.destination ?? lmc.default.destination ), getSourceLayer( + dependencies, title, id, layerGroupDescriptor.id, @@ -155,60 +164,65 @@ export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => { * @param layerDetails layer-specific field details */ export const getSourceLayer = ( + dependencies: LayerProviderDependencies, indexPatternTitle: string, indexPatternId: string, parentId: string, layerDetails: LayerMappingDetails -) => ({ - sourceDescriptor: { - id: uuidv4(), - type: 'ES_SEARCH', - applyGlobalQuery: true, - geoField: layerDetails.geoField, - filterByMapBounds: false, - tooltipProperties: layerDetails.tooltipProperties, - useTopHits: false, - topHitsTimeField: '@timestamp', - topHitsSize: 1, - indexPatternId, - }, - style: { - type: 'VECTOR', - properties: { - fillColor: { - type: 'STATIC', - options: { color: euiVisColorPalette[1] }, - }, - lineColor: { - type: 'STATIC', - options: { color: '#FFFFFF' }, - }, - lineWidth: { type: 'STATIC', options: { size: 2 } }, - iconSize: { type: 'STATIC', options: { size: 8 } }, - iconOrientation: { - type: 'STATIC', - options: { orientation: 0 }, - }, - symbolizeAs: { - options: { value: 'icon' }, - }, - icon: { - type: 'STATIC', - options: { value: 'home' }, +) => { + return { + sourceDescriptor: { + id: uuidv4(), + type: 'ES_SEARCH', + applyGlobalQuery: true, + geoField: layerDetails.geoField, + filterByMapBounds: false, + tooltipProperties: layerDetails.tooltipProperties, + useTopHits: false, + topHitsTimeField: '@timestamp', + topHitsSize: 1, + indexPatternId, + }, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { + color: dependencies.euiTheme.colors.vis.euiColorVis4, + }, + }, + lineColor: { + type: 'STATIC', + options: { color: dependencies.euiTheme.colors.vis.euiColorVisNeutral0 }, + }, + lineWidth: { type: 'STATIC', options: { size: 2 } }, + iconSize: { type: 'STATIC', options: { size: 8 } }, + iconOrientation: { + type: 'STATIC', + options: { orientation: 0 }, + }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'home' }, + }, }, }, - }, - id: uuidv4(), - parent: parentId, - label: `${indexPatternTitle} | ${layerDetails.label}`, - minZoom: 0, - maxZoom: 24, - alpha: 1, - visible: true, - type: LAYER_TYPE.GEOJSON_VECTOR, - query: { query: '', language: 'kuery' }, - joins: [], -}); + id: uuidv4(), + parent: parentId, + label: `${indexPatternTitle} | ${layerDetails.label}`, + minZoom: 0, + maxZoom: 24, + alpha: 1, + visible: true, + type: LAYER_TYPE.GEOJSON_VECTOR, + query: { query: '', language: 'kuery' }, + joins: [], + }; +}; /** * Returns Document Data Source layer configuration ('destination.geo.location') for the given @@ -221,60 +235,63 @@ export const getSourceLayer = ( * */ export const getDestinationLayer = ( + dependencies: LayerProviderDependencies, indexPatternTitle: string, indexPatternId: string, parentId: string, layerDetails: LayerMappingDetails -) => ({ - sourceDescriptor: { - id: uuidv4(), - type: 'ES_SEARCH', - scalingType: SCALING_TYPES.LIMIT, - applyGlobalQuery: true, - geoField: layerDetails.geoField, - filterByMapBounds: true, - tooltipProperties: layerDetails.tooltipProperties, - useTopHits: false, - topHitsTimeField: '@timestamp', - topHitsSize: 1, - indexPatternId, - }, - style: { - type: 'VECTOR', - properties: { - fillColor: { - type: 'STATIC', - options: { color: euiVisColorPalette[2] }, - }, - lineColor: { - type: 'STATIC', - options: { color: '#FFFFFF' }, - }, - lineWidth: { type: 'STATIC', options: { size: 2 } }, - iconSize: { type: 'STATIC', options: { size: 8 } }, - iconOrientation: { - type: 'STATIC', - options: { orientation: 0 }, - }, - symbolizeAs: { - options: { value: 'icon' }, - }, - icon: { - type: 'STATIC', - options: { value: 'marker' }, +) => { + return { + sourceDescriptor: { + id: uuidv4(), + type: 'ES_SEARCH', + scalingType: SCALING_TYPES.LIMIT, + applyGlobalQuery: true, + geoField: layerDetails.geoField, + filterByMapBounds: true, + tooltipProperties: layerDetails.tooltipProperties, + useTopHits: false, + topHitsTimeField: '@timestamp', + topHitsSize: 1, + indexPatternId, + }, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { color: dependencies.euiTheme.colors.vis.euiColorVis2 }, + }, + lineColor: { + type: 'STATIC', + options: { color: dependencies.euiTheme.colors.vis.euiColorVisNeutral0 }, + }, + lineWidth: { type: 'STATIC', options: { size: 2 } }, + iconSize: { type: 'STATIC', options: { size: 8 } }, + iconOrientation: { + type: 'STATIC', + options: { orientation: 0 }, + }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'marker' }, + }, }, }, - }, - id: uuidv4(), - parent: parentId, - label: `${indexPatternTitle} | ${layerDetails.label}`, - minZoom: 0, - maxZoom: 24, - alpha: 1, - visible: true, - type: LAYER_TYPE.GEOJSON_VECTOR, - query: { query: '', language: 'kuery' }, -}); + id: uuidv4(), + parent: parentId, + label: `${indexPatternTitle} | ${layerDetails.label}`, + minZoom: 0, + maxZoom: 24, + alpha: 1, + visible: true, + type: LAYER_TYPE.GEOJSON_VECTOR, + query: { query: '', language: 'kuery' }, + }; +}; /** * Returns Point-to-point Data Source layer configuration ('source.geo.location' & @@ -290,75 +307,79 @@ export const getLineLayer = ( indexPatternId: string, parentId: string, layerDetails: LayerMapping -) => ({ - sourceDescriptor: { - type: SOURCE_TYPES.ES_PEW_PEW, - applyGlobalQuery: true, - id: uuidv4(), - indexPatternId, - sourceGeoField: layerDetails.source.geoField, - destGeoField: layerDetails.destination.geoField, - metrics: [ - { - type: 'sum', - field: layerDetails.source.metricField, - label: layerDetails.source.metricField, - }, - { - type: 'sum', - field: layerDetails.destination.metricField, - label: layerDetails.destination.metricField, - }, - ], - }, - style: { - type: 'VECTOR', - properties: { - fillColor: { - type: 'STATIC', - options: { color: '#1EA593' }, - }, - lineColor: { - type: 'STATIC', - options: { color: euiVisColorPalette[1] }, - }, - lineWidth: { - type: 'DYNAMIC', - options: { - field: { - label: 'count', - name: 'doc_count', - origin: 'source', - }, - minSize: 1, - maxSize: 8, - fieldMetaOptions: { - isEnabled: true, - sigma: 3, +) => { + const euiVisColorPalette = euiPaletteColorBlind(); + + return { + sourceDescriptor: { + type: SOURCE_TYPES.ES_PEW_PEW, + applyGlobalQuery: true, + id: uuidv4(), + indexPatternId, + sourceGeoField: layerDetails.source.geoField, + destGeoField: layerDetails.destination.geoField, + metrics: [ + { + type: 'sum', + field: layerDetails.source.metricField, + label: layerDetails.source.metricField, + }, + { + type: 'sum', + field: layerDetails.destination.metricField, + label: layerDetails.destination.metricField, + }, + ], + }, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { color: euiVisColorPalette[1] }, + }, + lineColor: { + type: 'STATIC', + options: { color: euiVisColorPalette[1] }, + }, + lineWidth: { + type: 'DYNAMIC', + options: { + field: { + label: 'count', + name: 'doc_count', + origin: 'source', + }, + minSize: 1, + maxSize: 8, + fieldMetaOptions: { + isEnabled: true, + sigma: 3, + }, }, }, - }, - iconSize: { type: 'STATIC', options: { size: 10 } }, - iconOrientation: { - type: 'STATIC', - options: { orientation: 0 }, - }, - symbolizeAs: { - options: { value: 'icon' }, - }, - icon: { - type: 'STATIC', - options: { value: 'airfield' }, + iconSize: { type: 'STATIC', options: { size: 10 } }, + iconOrientation: { + type: 'STATIC', + options: { orientation: 0 }, + }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: 'STATIC', + options: { value: 'airfield' }, + }, }, }, - }, - id: uuidv4(), - parent: parentId, - label: `${indexPatternTitle} | ${i18n.LINE_LAYER}`, - minZoom: 0, - maxZoom: 24, - alpha: 0.5, - visible: true, - type: LAYER_TYPE.GEOJSON_VECTOR, - query: { query: '', language: 'kuery' }, -}); + id: uuidv4(), + parent: parentId, + label: `${indexPatternTitle} | ${i18n.LINE_LAYER}`, + minZoom: 0, + maxZoom: 24, + alpha: 0.5, + visible: true, + type: LAYER_TYPE.GEOJSON_VECTOR, + query: { query: '', language: 'kuery' }, + }; +};