From 62bd65959091b2c4aa7a0b24f57bade5f320f0a4 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 8 Dec 2022 15:15:43 +0000 Subject: [PATCH 01/72] render histogram with lens --- .../events_tab/histogram_configurations.ts | 2 +- .../components/matrix_histogram/index.tsx | 45 ++++++++++++++++--- .../__snapshots__/event.test.ts.snap | 9 ++-- .../{hosts => common}/event.test.ts | 0 .../{hosts => common}/events.ts | 9 ++-- .../components/visualization_actions/types.ts | 2 +- .../containers/matrix_histogram/index.ts | 5 ++- 7 files changed, 50 insertions(+), 22 deletions(-) rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/{hosts => common}/__snapshots__/event.test.ts.snap (96%) rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/{hosts => common}/event.test.ts (100%) rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/{hosts => common}/events.ts (95%) diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/histogram_configurations.ts b/x-pack/plugins/security_solution/public/common/components/events_tab/histogram_configurations.ts index b1e23d518c6b6f..f341f090710a04 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_tab/histogram_configurations.ts +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/histogram_configurations.ts @@ -8,7 +8,7 @@ import numeral from '@elastic/numeral'; import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution'; import { getExternalAlertLensAttributes } from '../visualization_actions/lens_attributes/common/external_alert'; -import { getEventsHistogramLensAttributes } from '../visualization_actions/lens_attributes/hosts/events'; +import { getEventsHistogramLensAttributes } from '../visualization_actions/lens_attributes/common/events'; import type { MatrixHistogramConfigs, MatrixHistogramOption } from '../matrix_histogram/types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index 82a37d20b1cfee..10bb49e2df3fd0 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -36,6 +36,9 @@ import type { GetLensAttributes, LensAttributes } from '../visualization_actions import { SecurityPageName } from '../../../../common/constants'; import { useRouteSpy } from '../../utils/route/use_route_spy'; import { useQueryToggle } from '../../containers/query_toggle'; +import { LensEmbeddable } from '../visualization_actions/lens_embeddable'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; +import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; export type MatrixHistogramComponentProps = MatrixHistogramProps & Omit & { @@ -69,6 +72,9 @@ const HistogramPanel = styled(Panel)<{ height?: number }>` ${({ height }) => (height != null ? `min-height: ${height}px;` : '')} `; +const ChartHeight = '150px'; + +// eslint-disable-next-line complexity export const MatrixHistogramComponent: React.FC = ({ chartHeight, defaultStackByOption, @@ -164,6 +170,12 @@ export const MatrixHistogramComponent: React.FC = [setQuerySkip, setToggleStatus] ); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: id, + }); + const matrixHistogramRequest = { endDate, errorMessage, @@ -175,7 +187,7 @@ export const MatrixHistogramComponent: React.FC = stackByField: selectedStackByOption.value, runtimeMappings, isPtrIncluded, - skip: querySkip, + skip: querySkip || isChartEmbeddablesEnabled, }; const [loading, { data, inspect, totalCount, refetch }] = useMatrixHistogramCombined(matrixHistogramRequest); @@ -209,22 +221,31 @@ export const MatrixHistogramComponent: React.FC = useEffect(() => { if (!loading && !isInitialLoading) { - setQuery({ id, inspect, loading, refetch }); + setQuery({ + id, + inspect, + loading, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + searchSessionId, + }); } if (isInitialLoading && !!barChartData && data) { setIsInitialLoading(false); } }, [ - setQuery, + barChartData, + data, id, inspect, + isChartEmbeddablesEnabled, + isInitialLoading, loading, refetch, - isInitialLoading, - barChartData, - data, + refetchByRestartingSession, + searchSessionId, setIsInitialLoading, + setQuery, ]); const timerange = useMemo(() => ({ from: startDate, to: endDate }), [startDate, endDate]); @@ -293,7 +314,17 @@ export const MatrixHistogramComponent: React.FC = {toggleStatus ? ( - isInitialLoading ? ( + isChartEmbeddablesEnabled && (getLensAttributes || lensAttributes) && timerange ? ( + + ) : isInitialLoading ? ( ) : ( ({ - title: 'Host - events', + title: 'Events', description: '', visualizationType: 'lnsXY', state: { @@ -20,6 +20,7 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( legend: { isVisible: true, position: 'right', + isInside: true, }, valueLabels: 'hide', preferredSeriesType: 'bar_stacked', @@ -46,6 +47,7 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( yLeft: false, yRight: true, }, + valuesInLegend: true, }, query: { query: '', @@ -110,11 +112,6 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( }, }, references: [ - { - type: 'index-pattern', - id: '{dataViewId}', - name: 'indexpattern-datasource-current-indexpattern', - }, { type: 'index-pattern', id: '{dataViewId}', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index b70634cb6f4d2e..f1f002449429d2 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -32,7 +32,7 @@ export interface LensEmbeddableComponentProps { id: string; inputsModelId?: InputsModelId.global | InputsModelId.timeline; inspectTitle?: string; - lensAttributes: LensAttributes; + lensAttributes?: LensAttributes; stackByField?: string; timerange: { from: string; to: string }; } diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index 85512855580c31..f75385fdd49553 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -249,7 +249,10 @@ export const useMatrixHistogramCombined = ( const [missingDataLoading, missingDataResponse] = useMatrixHistogram({ ...matrixHistogramQueryProps, includeMissingData: false, - skip: skipMissingData || matrixHistogramQueryProps.filterQuery === undefined, + skip: + skipMissingData || + matrixHistogramQueryProps.filterQuery === undefined || + matrixHistogramQueryProps.skip, }); const combinedLoading = useMemo( From 6376733a83998a2f1ac128cdd1438f7b64dd7bac Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 16 Dec 2022 09:58:15 +0000 Subject: [PATCH 02/72] replace alert histograms with lens --- .../components/alerts_treemap_panel/index.tsx | 5 +- .../lens_attributes/common/alerts.ts | 143 ++++++++++++++++++ .../visualization_actions/lens_embeddable.tsx | 5 + .../use_lens_attributes.tsx | 5 +- .../alerts_histogram_panel/index.tsx | 34 ++++- .../components/alerts_kpis/common/hooks.ts | 5 +- 6 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts.ts diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index 75c78a3be86413..c2b4e9d1e7fa42 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -24,6 +24,8 @@ import { HeaderSection } from '../header_section'; import { InspectButtonContainer } from '../inspect'; import { DEFAULT_STACK_BY_FIELD0_SIZE, getAlertsRiskQuery } from '../alerts_treemap/query'; import type { AlertsTreeMapAggregation } from '../alerts_treemap/types'; +import { InputsModelId } from '../../store/inputs/constants'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; const DEFAULT_HEIGHT = DEFAULT_MIN_CHART_HEIGHT + 134; // px @@ -149,9 +151,10 @@ const AlertsTreemapPanelComponent: React.FC = ({ loading: isLoadingAlerts, response, setQuery, - refetch, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, request, uniqueQueryId, + searchSessionId, }); return ( diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts.ts new file mode 100644 index 00000000000000..8a39a4462d2766 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts.ts @@ -0,0 +1,143 @@ +/* + * 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 type { GetLensAttributes, LensAttributes } from '../../types'; + +export const getAlertsHistogramLensAttributes: GetLensAttributes = ( + stackByField = 'kibana.alert.rule.name' +) => + ({ + title: 'Alerts', + description: '', + visualizationType: 'lnsXY', + state: { + visualization: { + title: 'Empty XY chart', + legend: { + isVisible: true, + position: 'right', + isInside: true, + }, + valueLabels: 'hide', + preferredSeriesType: 'bar_stacked', + layers: [ + { + layerId: '0039eb0c-9a1a-4687-ae54-0f4e239bec75', + accessors: ['e09e0380-0740-4105-becc-0a4ca12e3944'], + position: 'top', + seriesType: 'bar_stacked', + showGridlines: false, + layerType: 'data', + xAccessor: 'aac9d7d0-13a3-480a-892b-08207a787926', + splitAccessor: '34919782-4546-43a5-b668-06ac934d3acd', + }, + ], + yRightExtent: { + mode: 'full', + }, + yLeftExtent: { + mode: 'full', + }, + axisTitlesVisibilitySettings: { + x: false, + yLeft: false, + yRight: true, + }, + valuesInLegend: true, + }, + query: { + query: '', + language: 'kuery', + }, + filters: [ + { + meta: { + index: '.alerts-security.alerts-id', + alias: null, + negate: true, + disabled: false, + type: 'exists', + key: 'kibana.alert.building_block_type', + }, + query: { + exists: { + field: 'kibana.alert.building_block_type', + }, + }, + $state: { + store: 'appState', + }, + }, + ], + datasourceStates: { + formBased: { + layers: { + '0039eb0c-9a1a-4687-ae54-0f4e239bec75': { + columns: { + 'aac9d7d0-13a3-480a-892b-08207a787926': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { + interval: 'auto', + }, + }, + 'e09e0380-0740-4105-becc-0a4ca12e3944': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + }, + '34919782-4546-43a5-b668-06ac934d3acd': { + label: `Top values of ${stackByField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: `${stackByField}`, + isBucketed: true, + params: { + size: 10, + orderBy: { + type: 'column', + columnId: 'e09e0380-0740-4105-becc-0a4ca12e3944', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + secondaryFields: [], + }, + }, + }, + columnOrder: [ + '34919782-4546-43a5-b668-06ac934d3acd', + 'aac9d7d0-13a3-480a-892b-08207a787926', + 'e09e0380-0740-4105-becc-0a4ca12e3944', + ], + incompleteColumns: {}, + }, + }, + }, + }, + internalReferences: [], + adHocDataViews: {}, + }, + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: 'indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75', + }, + ], + } as LensAttributes); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 7c03e2886a62ba..b834a06f4b2382 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -20,6 +20,7 @@ import { useDeepEqualSelector } from '../../hooks/use_selector'; import { ModalInspectQuery } from '../inspect/modal'; import { InputsModelId } from '../../store/inputs/constants'; import { getRequestsAndResponses } from './utils'; +import { SourcererScopeName } from '../../store/sourcerer/model'; const LensComponentWrapper = styled.div<{ height?: string }>` height: ${({ height }) => height ?? 'auto'}; @@ -52,6 +53,7 @@ const LensEmbeddableComponent: React.FC = ({ id, inputsModelId = InputsModelId.global, lensAttributes, + scopeId = SourcererScopeName.default, stackByField, timerange, inspectTitle, @@ -67,6 +69,7 @@ const LensEmbeddableComponent: React.FC = ({ getLensAttributes, stackByField, title: '', + scopeId, }); const LensComponent = lens.EmbeddableComponent; @@ -127,6 +130,8 @@ const LensEmbeddableComponent: React.FC = ({ }); }, []); + console.log(id, searchSessionId); + return ( <> {attributes && searchSessionId ? ( diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index 43c96825cd0260..4b2945d58cd740 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -12,6 +12,7 @@ import { NetworkRouteType } from '../../../network/pages/navigation/types'; import { useSourcererDataView } from '../../containers/sourcerer'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { inputsSelectors } from '../../store'; +import { SourcererScopeName } from '../../store/sourcerer/model'; import { useRouteSpy } from '../../utils/route/use_route_spy'; import type { LensAttributes, GetLensAttributes } from './types'; import { @@ -26,13 +27,15 @@ export const useLensAttributes = ({ getLensAttributes, stackByField, title, + scopeId = SourcererScopeName.default, }: { lensAttributes?: LensAttributes | null; getLensAttributes?: GetLensAttributes; stackByField?: string; title?: string; + scopeId?: SourcererScopeName; }): LensAttributes | null => { - const { selectedPatterns, dataViewId } = useSourcererDataView(); + const { selectedPatterns, dataViewId } = useSourcererDataView(scopeId); const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); const getGlobalFiltersQuerySelector = useMemo( () => inputsSelectors.globalFiltersQuerySelector(), diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index 688161019732d5..4f4a4979d0a377 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -50,6 +50,12 @@ import { KpiPanel, StackByComboBox } from '../common/components'; import { useInspectButton } from '../common/hooks'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { GROUP_BY_TOP_LABEL } from '../common/translations'; +import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { getAlertsHistogramLensAttributes as getLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; const defaultTotalAlertsObj: AlertsTotal = { value: 0, @@ -68,6 +74,8 @@ const OptionsFlexItem = styled(EuiFlexItem)` export const LEGEND_WITH_COUNTS_WIDTH = 300; // px +const ChartHeight = '150px'; + interface AlertsHistogramPanelProps { alignHeader?: 'center' | 'baseline' | 'stretch' | 'flexStart' | 'flexEnd'; chartHeight?: number; @@ -175,6 +183,14 @@ export const AlertsHistogramPanel = memo( }, [setQuerySkip, setToggleStatus] ); + + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const timerange = useMemo(() => ({ from, to }), [from, to]); + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: uniqueQueryId, + }); + const { loading: isLoadingAlerts, data: alertsData, @@ -191,7 +207,7 @@ export const AlertsHistogramPanel = memo( runtimeMappings ), indexName: signalIndexName, - skip: querySkip, + skip: querySkip || isChartEmbeddablesEnabled, queryName: ALERTS_QUERY_NAMES.HISTOGRAM, }); @@ -261,10 +277,11 @@ export const AlertsHistogramPanel = memo( setQuery, response, request, - refetch, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, uniqueQueryId, deleteQuery, loading: isLoadingAlerts, + searchSessionId, }); useEffect(() => { @@ -401,7 +418,18 @@ export const AlertsHistogramPanel = memo( {toggleStatus ? ( - isInitialLoading ? ( + isChartEmbeddablesEnabled && (getLensAttributes || lensAttributes) && timerange ? ( + + ) : isInitialLoading ? ( ) : ( void) | null; uniqueQueryId: string; loading: boolean; + searchSessionId: string; } /** @@ -33,6 +34,7 @@ export const useInspectButton = ({ uniqueQueryId, deleteQuery, loading, + searchSessionId, }: UseInspectButtonParams) => { useEffect(() => { if (refetch != null && setQuery != null) { @@ -44,6 +46,7 @@ export const useInspectButton = ({ }, loading, refetch, + searchSessionId, }); } @@ -52,7 +55,7 @@ export const useInspectButton = ({ deleteQuery({ id: uniqueQueryId }); } }; - }, [setQuery, loading, response, request, refetch, uniqueQueryId, deleteQuery]); + }, [setQuery, loading, response, request, refetch, uniqueQueryId, deleteQuery, searchSessionId]); }; export function getAggregatableFields(fields: { From b68afd142f7ec81b1c74180a1030015c8346bdc4 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 20 Dec 2022 15:15:31 +0000 Subject: [PATCH 03/72] replace alerts histograms with Lens --- .../components/alerts_treemap_panel/index.tsx | 75 ++++++++-- .../components/matrix_histogram/index.tsx | 13 +- .../{alerts.ts => alerts/alerts_histogram.ts} | 34 ++--- .../common/alerts/alerts_table.ts | 140 ++++++++++++++++++ .../common/alerts/alerts_treemap.ts | 137 +++++++++++++++++ .../lens_attributes/common/alerts/utils.ts | 76 ++++++++++ .../lens_attributes/common/events.ts | 6 +- .../visualization_actions/lens_embeddable.tsx | 4 +- .../components/visualization_actions/types.ts | 16 +- .../use_lens_attributes.tsx | 9 +- .../pages/rule_details/index.tsx | 3 + .../alerts_kpis/alerts_count_panel/index.tsx | 74 +++++++-- .../alerts_histogram_panel/index.test.tsx | 2 + .../alerts_histogram_panel/index.tsx | 35 +++-- .../detection_engine/chart_panels/index.tsx | 19 ++- .../detection_engine/detection_engine.tsx | 3 + .../signals_by_category.tsx | 2 + 17 files changed, 565 insertions(+), 83 deletions(-) rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/{alerts.ts => alerts/alerts_histogram.ts} (84%) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index c2b4e9d1e7fa42..459e54904779c9 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -26,6 +26,11 @@ import { DEFAULT_STACK_BY_FIELD0_SIZE, getAlertsRiskQuery } from '../alerts_tree import type { AlertsTreeMapAggregation } from '../alerts_treemap/types'; import { InputsModelId } from '../../store/inputs/constants'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; +import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; +import { LensEmbeddable } from '../visualization_actions/lens_embeddable'; +import { getAlertsTreemapLensAttributes as getLensAttributes } from '../visualization_actions/lens_attributes/common/alerts/alerts_treemap'; +import { SourcererScopeName } from '../../store/sourcerer/model'; +import type { Status } from '../../../../common/detection_engine/schemas/common'; const DEFAULT_HEIGHT = DEFAULT_MIN_CHART_HEIGHT + 134; // px @@ -56,6 +61,9 @@ export interface Props { stackByField1ComboboxRef?: React.RefObject>; stackByWidth?: number; title: React.ReactNode; + showBuildingBlockAlerts: boolean; + status: Status; + showOnlyThreatIndicatorAlerts: boolean; } const AlertsTreemapPanelComponent: React.FC = ({ @@ -81,6 +89,9 @@ const AlertsTreemapPanelComponent: React.FC = ({ stackByField1ComboboxRef, stackByWidth, title, + showBuildingBlockAlerts, + showOnlyThreatIndicatorAlerts, + status, }: Props) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -146,6 +157,22 @@ const AlertsTreemapPanelComponent: React.FC = ({ to, ]); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const timerange = useMemo(() => ({ from, to }), [from, to]); + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: uniqueQueryId, + }); + const alertsOptions = useMemo( + () => ({ + showBuildingBlockAlerts, + showOnlyThreatIndicatorAlerts, + status, + breakdownField: stackByField1, + }), + [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status, stackByField1] + ); + useInspectButton({ deleteQuery, loading: isLoadingAlerts, @@ -181,7 +208,9 @@ const AlertsTreemapPanelComponent: React.FC = ({ > {isPanelExpanded && ( = ({ )} - {isLoadingAlerts && isPanelExpanded ? ( - - ) : ( - <> - {alertsData != null && isPanelExpanded && ( - - )} - - )} + {isPanelExpanded ? ( + isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( + + ) : isLoadingAlerts ? ( + + ) : ( + <> + {alertsData != null && isPanelExpanded && ( + + )} + + ) + ) : null} ); diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index 10bb49e2df3fd0..c5e1f7f2144c41 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -33,8 +33,6 @@ import { InputsModelId } from '../../store/inputs/constants'; import { HoverVisibilityContainer } from '../hover_visibility_container'; import { HISTOGRAM_ACTIONS_BUTTON_CLASS, VisualizationActions } from '../visualization_actions'; import type { GetLensAttributes, LensAttributes } from '../visualization_actions/types'; -import { SecurityPageName } from '../../../../common/constants'; -import { useRouteSpy } from '../../utils/route/use_route_spy'; import { useQueryToggle } from '../../containers/query_toggle'; import { LensEmbeddable } from '../visualization_actions/lens_embeddable'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; @@ -74,7 +72,6 @@ const HistogramPanel = styled(Panel)<{ height?: number }>` const ChartHeight = '150px'; -// eslint-disable-next-line complexity export const MatrixHistogramComponent: React.FC = ({ chartHeight, defaultStackByOption, @@ -191,12 +188,6 @@ export const MatrixHistogramComponent: React.FC = }; const [loading, { data, inspect, totalCount, refetch }] = useMatrixHistogramCombined(matrixHistogramRequest); - const [{ pageName }] = useRouteSpy(); - - const onHostOrNetworkOrUserPage = - pageName === SecurityPageName.hosts || - pageName === SecurityPageName.network || - pageName === SecurityPageName.users; const titleWithStackByField = useMemo( () => (title != null && typeof title === 'function' ? title(selectedStackByOption) : title), @@ -282,11 +273,11 @@ export const MatrixHistogramComponent: React.FC = toggleQuery={toggleQuery} subtitle={subtitleWithCounts} inspectMultiple - showInspectButton={showInspectButton || !onHostOrNetworkOrUserPage} + showInspectButton={showInspectButton && !isChartEmbeddablesEnabled} isInspectDisabled={filterQuery === undefined} > - {onHostOrNetworkOrUserPage && (getLensAttributes || lensAttributes) && timerange && ( + {(getLensAttributes || lensAttributes) && timerange && ( ({ title: 'Alerts', @@ -53,26 +58,7 @@ export const getAlertsHistogramLensAttributes: GetLensAttributes = ( query: '', language: 'kuery', }, - filters: [ - { - meta: { - index: '.alerts-security.alerts-id', - alias: null, - negate: true, - disabled: false, - type: 'exists', - key: 'kibana.alert.building_block_type', - }, - query: { - exists: { - field: 'kibana.alert.building_block_type', - }, - }, - $state: { - store: 'appState', - }, - }, - ], + filters: buildAlertsOptionsFilters(alertsOptions), datasourceStates: { formBased: { layers: { @@ -102,10 +88,10 @@ export const getAlertsHistogramLensAttributes: GetLensAttributes = ( dataType: 'string', operationType: 'terms', scale: 'ordinal', - sourceField: `${stackByField}`, + sourceField: stackByField, isBucketed: true, params: { - size: 10, + size: 1000, orderBy: { type: 'column', columnId: 'e09e0380-0740-4105-becc-0a4ca12e3944', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts new file mode 100644 index 00000000000000..c3cde0b93860ff --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts @@ -0,0 +1,140 @@ +/* + * 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 type { GetLensAttributes } from '../../../types'; +import { buildAlertsOptionsFilters } from './utils'; + +export const getAlertsTableLensAttributes: GetLensAttributes = ( + stackByField = 'kibana.alert.rule.name', + alertsOptions = { + showOnlyThreatIndicatorAlerts: false, + showBuildingBlockAlerts: false, + } +) => ({ + title: 'Alerts', + description: '', + visualizationType: 'lnsDatatable', + state: { + visualization: { + columns: [ + { + columnId: '2881fedd-54b7-42ba-8c97-5175dec86166', + isTransposed: false, + width: 362, + }, + { + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + isTransposed: false, + }, + { + columnId: '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + isTransposed: false, + }, + ], + layerId: '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', + layerType: 'data', + paging: { + size: 10, + enabled: true, + }, + }, + query: { + query: '', + language: 'kuery', + }, + filters: buildAlertsOptionsFilters(alertsOptions), + datasourceStates: { + formBased: { + layers: { + '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b': { + columns: { + '2881fedd-54b7-42ba-8c97-5175dec86166': { + label: `Top values of ${stackByField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: stackByField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, + 'f04a71a3-399f-4d32-9efc-8a005e989991': { + label: `Count of ${alertsOptions.breakdownField}`, + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: alertsOptions.breakdownField, + params: { + emptyAsNull: true, + }, + }, + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { + label: `Top values of ${alertsOptions.breakdownField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: alertsOptions.breakdownField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, + }, + columnOrder: [ + '2881fedd-54b7-42ba-8c97-5175dec86166', + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + 'f04a71a3-399f-4d32-9efc-8a005e989991', + ], + sampling: 1, + incompleteColumns: {}, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [], + adHocDataViews: {}, + }, + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: 'indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', + }, + ], +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts new file mode 100644 index 00000000000000..12c45b97df0b4f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts @@ -0,0 +1,137 @@ +/* + * 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 type { GetLensAttributes } from '../../../types'; +import { buildAlertsOptionsFilters } from './utils'; + +export const getAlertsTreemapLensAttributes: GetLensAttributes = ( + stackByField = 'kibana.alert.rule.name', + alertsOptions = { + showOnlyThreatIndicatorAlerts: false, + showBuildingBlockAlerts: false, + } +) => ({ + title: 'Alerts', + description: '', + visualizationType: 'lnsPie', + state: { + visualization: { + shape: 'treemap', + layers: [ + { + layerId: '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', + primaryGroups: [ + '2881fedd-54b7-42ba-8c97-5175dec86166', + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + ], + metrics: ['f04a71a3-399f-4d32-9efc-8a005e989991'], + numberDisplay: 'value', + categoryDisplay: 'default', + legendDisplay: 'show', + nestedLegend: true, + layerType: 'data', + legendSize: 'xlarge', + legendPosition: 'left', + }, + ], + }, + query: { + query: '', + language: 'kuery', + }, + filters: buildAlertsOptionsFilters(alertsOptions), + datasourceStates: { + formBased: { + layers: { + '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b': { + columns: { + '2881fedd-54b7-42ba-8c97-5175dec86166': { + label: `Top values of ${stackByField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: stackByField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { + label: `Top values of ${alertsOptions.breakdownField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: alertsOptions.breakdownField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, + 'f04a71a3-399f-4d32-9efc-8a005e989991': { + label: 'Maximum of kibana.alert.risk_score', + dataType: 'number', + operationType: 'max', + sourceField: 'kibana.alert.risk_score', + isBucketed: false, + scale: 'ratio', + params: { + emptyAsNull: true, + }, + }, + }, + columnOrder: [ + '2881fedd-54b7-42ba-8c97-5175dec86166', + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + 'f04a71a3-399f-4d32-9efc-8a005e989991', + ], + sampling: 1, + incompleteColumns: {}, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [], + adHocDataViews: {}, + }, + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: 'indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', + }, + ], +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts new file mode 100644 index 00000000000000..86caecf3138e7a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts @@ -0,0 +1,76 @@ +/* + * 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 type { AlertsOptions } from '../../../types'; + +export const buildAlertsOptionsFilters = ({ + showBuildingBlockAlerts = false, + showOnlyThreatIndicatorAlerts = false, + status, +}: AlertsOptions) => [ + ...(status + ? [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'kibana.alert.workflow_status', + params: { + query: status, + }, + }, + query: { + match_phrase: { + 'kibana.alert.workflow_status': status, + }, + }, + }, + ] + : []), + ...(showOnlyThreatIndicatorAlerts + ? [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'kibana.alert.rule.type', + params: { + query: 'threat_match', + }, + }, + query: { + match_phrase: { + 'kibana.alert.rule.type': 'threat_match', + }, + }, + }, + ] + : []), + ...(!showBuildingBlockAlerts + ? [ + { + meta: { + index: '.alerts-security.alerts-id', + alias: null, + negate: true, + disabled: false, + type: 'exists', + key: 'kibana.alert.building_block_type', + }, + query: { + exists: { + field: 'kibana.alert.building_block_type', + }, + }, + }, + ] + : []), +]; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts index caf534f44f78fd..732f157ae3a19f 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts @@ -79,11 +79,11 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( sourceField: '___records___', }, '34919782-4546-43a5-b668-06ac934d3acd': { - label: `Top values of ${stackByField}`, // could be event.dataset or event.module + label: `Top values of ${stackByField}`, dataType: 'string', operationType: 'terms', scale: 'ordinal', - sourceField: `${stackByField}`, // could be event.dataset or event.module + sourceField: `${stackByField}`, isBucketed: true, params: { size: 10, @@ -91,7 +91,7 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( type: 'column', columnId: 'e09e0380-0740-4105-becc-0a4ca12e3944', }, - orderDirection: 'asc', + orderDirection: 'desc', otherBucket: true, missingBucket: false, parentFormat: { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index b834a06f4b2382..eb2e8a2513468d 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -57,6 +57,7 @@ const LensEmbeddableComponent: React.FC = ({ stackByField, timerange, inspectTitle, + alertsOptions, }) => { const { lens } = useKibana().services; const dispatch = useDispatch(); @@ -70,6 +71,7 @@ const LensEmbeddableComponent: React.FC = ({ stackByField, title: '', scopeId, + alertsOptions, }); const LensComponent = lens.EmbeddableComponent; @@ -130,8 +132,6 @@ const LensEmbeddableComponent: React.FC = ({ }); }, []); - console.log(id, searchSessionId); - return ( <> {attributes && searchSessionId ? ( diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index f1f002449429d2..f22af1f0d3bc8c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -6,10 +6,15 @@ */ import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import type { Status } from '../../../../common/detection_engine/schemas/common'; import type { InputsModelId } from '../../store/inputs/constants'; +import type { SourcererScopeName } from '../../store/sourcerer/model'; export type LensAttributes = TypedLensByValueInput['attributes']; -export type GetLensAttributes = (stackByField?: string) => LensAttributes; +export type GetLensAttributes = ( + stackByField?: string, + alertsOptions?: AlertsOptions +) => LensAttributes; export interface VisualizationActionsProps { className?: string; @@ -33,8 +38,10 @@ export interface LensEmbeddableComponentProps { inputsModelId?: InputsModelId.global | InputsModelId.timeline; inspectTitle?: string; lensAttributes?: LensAttributes; + scopeId?: SourcererScopeName; stackByField?: string; timerange: { from: string; to: string }; + alertsOptions?: AlertsOptions; } export enum RequestStatus { @@ -74,3 +81,10 @@ export interface Response { json?: { rawResponse?: object }; time?: number; } + +export interface AlertsOptions { + showBuildingBlockAlerts?: boolean; + showOnlyThreatIndicatorAlerts?: boolean; + status?: Status; + breakdownField?: string; +} diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index 3d6cdecdb2d8a9..708a6e43b1d305 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -14,7 +14,7 @@ import { useDeepEqualSelector } from '../../hooks/use_selector'; import { inputsSelectors } from '../../store'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { useRouteSpy } from '../../utils/route/use_route_spy'; -import type { LensAttributes, GetLensAttributes } from './types'; +import type { LensAttributes, GetLensAttributes, AlertsOptions } from './types'; import { getHostDetailsPageFilter, sourceOrDestinationIpExistsFilter, @@ -28,12 +28,14 @@ export const useLensAttributes = ({ stackByField, title, scopeId = SourcererScopeName.default, + alertsOptions, }: { lensAttributes?: LensAttributes | null; getLensAttributes?: GetLensAttributes; stackByField?: string; title?: string; scopeId?: SourcererScopeName; + alertsOptions?: AlertsOptions; }): LensAttributes | null => { const { selectedPatterns, dataViewId } = useSourcererDataView(scopeId); const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); @@ -72,7 +74,9 @@ export const useLensAttributes = ({ } const attrs: LensAttributes = lensAttributes ?? - ((getLensAttributes && stackByField && getLensAttributes(stackByField)) as LensAttributes); + ((getLensAttributes && + stackByField && + getLensAttributes(stackByField, alertsOptions)) as LensAttributes); return { ...attrs, @@ -97,6 +101,7 @@ export const useLensAttributes = ({ lensAttributes, getLensAttributes, stackByField, + alertsOptions, title, query, filters, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index 2fcf2d63110d25..ed600ed2b1962d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -840,6 +840,9 @@ const RuleDetailsPageComponent: React.FC = ({ defaultStackByOption={defaultRuleStackByOption} updateDateRange={updateDateRangeCallback} runtimeMappings={runtimeMappings} + showBuildingBlockAlerts={showBuildingBlockAlerts} + status={filterGroup} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} /> diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index 8fceaece8ed2bf..bf2a79499331eb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -27,6 +27,13 @@ import { KpiPanel } from '../common/components'; import { useInspectButton } from '../common/hooks'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { FieldSelection } from '../../../../common/components/field_selection'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; +import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; +import { getAlertsTableLensAttributes as getLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_table'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import type { Status } from '../../../../../common/detection_engine/schemas/common'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; export const DETECTIONS_ALERTS_COUNT_ID = 'detections-alerts-count'; @@ -49,7 +56,11 @@ interface AlertsCountPanelProps { stackByWidth?: number; title?: React.ReactNode; runtimeMappings?: MappingRuntimeFields; + showBuildingBlockAlerts: boolean; + status: Status; + showOnlyThreatIndicatorAlerts: boolean; } +const ChartHeight = '180px'; export const AlertsCountPanel = memo( ({ @@ -71,6 +82,9 @@ export const AlertsCountPanel = memo( stackByField1ComboboxRef, stackByWidth, title = i18n.COUNT_TABLE_TITLE, + showBuildingBlockAlerts, + showOnlyThreatIndicatorAlerts, + status, }) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -100,14 +114,29 @@ export const AlertsCountPanel = memo( setQuerySkip(!toggleStatus); }, [toggleStatus]); const toggleQuery = useCallback( - (status: boolean) => { - setToggleStatus(status); + (newToggleStatus: boolean) => { + setToggleStatus(newToggleStatus); // toggle on = skipQuery false - setQuerySkip(!status); + setQuerySkip(!newToggleStatus); }, [setQuerySkip, setToggleStatus] ); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const timerange = useMemo(() => ({ from, to }), [from, to]); + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: uniqueQueryId, + }); + const alertsOptions = useMemo( + () => ({ + showBuildingBlockAlerts, + showOnlyThreatIndicatorAlerts, + status, + breakdownField: stackByField1, + }), + [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, stackByField1, status] + ); const { loading: isLoadingAlerts, data: alertsData, @@ -125,7 +154,7 @@ export const AlertsCountPanel = memo( runtimeMappings, }), indexName: signalIndexName, - skip: querySkip, + skip: querySkip || isChartEmbeddablesEnabled, queryName: ALERTS_QUERY_NAMES.COUNT, }); @@ -154,10 +183,11 @@ export const AlertsCountPanel = memo( setQuery, response, request, - refetch, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, uniqueQueryId, deleteQuery, loading: isLoadingAlerts, + searchSessionId, }); return ( @@ -181,7 +211,9 @@ export const AlertsCountPanel = memo( toggleQuery={toggleQuery} > ( uniqueQueryId={uniqueQueryId} /> - {toggleStatus && alertsData != null && ( - - )} + {toggleStatus ? ( + isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( + + ) : alertsData != null ? ( + + ) : null + ) : null} ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx index c47353aa04b06f..58205252d01e1b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx @@ -101,6 +101,8 @@ describe('AlertsHistogramPanel', () => { signalIndexName: 'signalIndexName', setQuery: jest.fn(), updateDateRange: jest.fn(), + showBuildingBlockAlerts: false, + showOnlyThreatIndicatorAlerts: false, }; const mockSetToggle = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index 4f4a4979d0a377..80aab2cf7d621b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -52,10 +52,12 @@ import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { GROUP_BY_TOP_LABEL } from '../common/translations'; import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; -import { getAlertsHistogramLensAttributes as getLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts'; +import { getAlertsHistogramLensAttributes as getLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram'; import { InputsModelId } from '../../../../common/store/inputs/constants'; import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { useCheckSignalIndex } from '../../../containers/detection_engine/alerts/use_check_signal_index'; +import type { Status } from '../../../../../common/detection_engine/schemas/common'; const defaultTotalAlertsObj: AlertsTotal = { value: 0, @@ -74,7 +76,7 @@ const OptionsFlexItem = styled(EuiFlexItem)` export const LEGEND_WITH_COUNTS_WIDTH = 300; // px -const ChartHeight = '150px'; +const ChartHeight = '170px'; interface AlertsHistogramPanelProps { alignHeader?: 'center' | 'baseline' | 'stretch' | 'flexStart' | 'flexEnd'; @@ -108,6 +110,9 @@ interface AlertsHistogramPanelProps { title?: React.ReactNode; updateDateRange: UpdateDateRange; runtimeMappings?: MappingRuntimeFields; + showBuildingBlockAlerts: boolean; + status?: Status; + showOnlyThreatIndicatorAlerts: boolean; } const NO_LEGEND_DATA: LegendItem[] = []; @@ -144,6 +149,9 @@ export const AlertsHistogramPanel = memo( updateDateRange, titleSize = 'm', runtimeMappings, + showBuildingBlockAlerts, + showOnlyThreatIndicatorAlerts, + status, }) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -176,10 +184,10 @@ export const AlertsHistogramPanel = memo( setQuerySkip(!toggleStatus); }, [toggleStatus]); const toggleQuery = useCallback( - (status: boolean) => { - setToggleStatus(status); + (newToggleStatus: boolean) => { + setToggleStatus(newToggleStatus); // toggle on = skipQuery false - setQuerySkip(!status); + setQuerySkip(!newToggleStatus); }, [setQuerySkip, setToggleStatus] ); @@ -190,6 +198,14 @@ export const AlertsHistogramPanel = memo( inputId: InputsModelId.global, queryId: uniqueQueryId, }); + const alertsOptions = useMemo( + () => ({ + showBuildingBlockAlerts, + showOnlyThreatIndicatorAlerts, + status, + }), + [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status] + ); const { loading: isLoadingAlerts, @@ -367,7 +383,7 @@ export const AlertsHistogramPanel = memo( titleSize={titleSize} toggleStatus={toggleStatus} toggleQuery={toggleQuery} - showInspectButton={chartOptionsContextMenu == null} + showInspectButton={isChartEmbeddablesEnabled ? false : chartOptionsContextMenu == null} subtitle={!isInitialLoading && showTotalAlertsCount && totalAlerts} isInspectDisabled={isInspectDisabled} hideSubtitle @@ -407,7 +423,7 @@ export const AlertsHistogramPanel = memo( )} {headerChildren != null && headerChildren} - {chartOptionsContextMenu != null && ( + {chartOptionsContextMenu != null && !isChartEmbeddablesEnabled && ( {chartOptionsContextMenu(uniqueQueryId)} @@ -418,16 +434,17 @@ export const AlertsHistogramPanel = memo( {toggleStatus ? ( - isChartEmbeddablesEnabled && (getLensAttributes || lensAttributes) && timerange ? ( + isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( ) : isInitialLoading ? ( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx index 83df1b0018b1fd..50caf3fb661f73 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx @@ -28,6 +28,7 @@ import { } from '../../../components/alerts_kpis/common/config'; import { AlertsCountPanel } from '../../../components/alerts_kpis/alerts_count_panel'; import { GROUP_BY_LABEL } from '../../../components/alerts_kpis/common/translations'; +import type { Status } from '../../../../../common/detection_engine/schemas/common'; const TABLE_PANEL_HEIGHT = 330; // px const TRENT_CHART_HEIGHT = 127; // px @@ -50,6 +51,9 @@ export interface Props { runtimeMappings: MappingRuntimeFields; signalIndexName: string | null; updateDateRangeCallback: UpdateDateRange; + showBuildingBlockAlerts: boolean; + showOnlyThreatIndicatorAlerts: boolean; + filterGroup: Status; } const ChartPanelsComponent: React.FC = ({ @@ -60,6 +64,9 @@ const ChartPanelsComponent: React.FC = ({ runtimeMappings, signalIndexName, updateDateRangeCallback, + showBuildingBlockAlerts, + filterGroup, + showOnlyThreatIndicatorAlerts, }: Props) => { const { alertViewSelection, @@ -138,6 +145,7 @@ const ChartPanelsComponent: React.FC = ({ [alertViewSelection, setAlertViewSelection] ); const isAlertsPageChartsEnabled = useIsExperimentalFeatureEnabled('alertsPageChartsEnabled'); + return (
{alertViewSelection === 'trend' && ( @@ -158,7 +166,7 @@ const ChartPanelsComponent: React.FC = ({ panelHeight={TREND_CHART_PANEL_HEIGHT} query={query} showCountsInLegend={true} - showGroupByPlaceholder={true} + showGroupByPlaceholder={false} showTotalAlertsCount={false} stackByLabel={GROUP_BY_LABEL} title={title} @@ -166,6 +174,9 @@ const ChartPanelsComponent: React.FC = ({ signalIndexName={signalIndexName} updateDateRange={updateDateRangeCallback} runtimeMappings={runtimeMappings} + showBuildingBlockAlerts={showBuildingBlockAlerts} + status={filterGroup} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} /> )} @@ -194,6 +205,9 @@ const ChartPanelsComponent: React.FC = ({ stackByField0={countTableStackBy0} stackByField1={countTableStackBy1} title={title} + showBuildingBlockAlerts={showBuildingBlockAlerts} + status={filterGroup} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} /> )} @@ -225,6 +239,9 @@ const ChartPanelsComponent: React.FC = ({ title={title} riskSubAggregationField="kibana.alert.risk_score" runtimeMappings={runtimeMappings} + showBuildingBlockAlerts={showBuildingBlockAlerts} + status={filterGroup} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} /> )} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index cff7c40a0fe804..a2056b94473477 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -355,6 +355,9 @@ const DetectionEnginePageComponent: React.FC = ({ runtimeMappings={runtimeMappings} signalIndexName={signalIndexName} updateDateRangeCallback={updateDateRangeCallback} + filterGroup={filterGroup} + showBuildingBlockAlerts={showBuildingBlockAlerts} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} /> diff --git a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx index 61869643860777..83882c9e7b171e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx @@ -86,6 +86,8 @@ const SignalsByCategoryComponent: React.FC = ({ title={i18n.ALERT_TREND} titleSize={onlyField == null ? 'm' : 's'} updateDateRange={updateDateRangeCallback} + showBuildingBlockAlerts={false} + showOnlyThreatIndicatorAlerts={false} /> ); }; From 5f5291db5301131109d29f9d8ec2ed97b3f6f8c2 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 20 Dec 2022 15:21:19 +0000 Subject: [PATCH 04/72] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../components/alerts_kpis/alerts_histogram_panel/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index 80aab2cf7d621b..4541ca013ac479 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -56,7 +56,6 @@ import { getAlertsHistogramLensAttributes as getLensAttributes } from '../../../ import { InputsModelId } from '../../../../common/store/inputs/constants'; import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import { useCheckSignalIndex } from '../../../containers/detection_engine/alerts/use_check_signal_index'; import type { Status } from '../../../../../common/detection_engine/schemas/common'; const defaultTotalAlertsObj: AlertsTotal = { From a07eefefde10eda44bb98a3d76eb6e8ddb32378b Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 20 Dec 2022 17:06:32 +0000 Subject: [PATCH 05/72] extra actions --- .../components/alerts_treemap_panel/index.tsx | 4 +++ .../visualization_actions/lens_embeddable.tsx | 2 ++ .../components/visualization_actions/types.ts | 3 ++ .../visualization_actions/use_actions.ts | 7 +++- .../alerts_kpis/alerts_count_panel/index.tsx | 4 +++ .../alerts_histogram_panel/index.tsx | 4 +++ .../detection_engine/chart_panels/index.tsx | 34 +++++++++++++++++++ 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index 459e54904779c9..ac365aa8a55ef1 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -6,6 +6,7 @@ */ import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; +import type { Action } from '@kbn/ui-actions-plugin/public'; import type { EuiComboBox } from '@elastic/eui'; import { EuiProgress } from '@elastic/eui'; import type { Filter, Query } from '@kbn/es-query'; @@ -64,6 +65,7 @@ export interface Props { showBuildingBlockAlerts: boolean; status: Status; showOnlyThreatIndicatorAlerts: boolean; + extraActions?: Action[]; } const AlertsTreemapPanelComponent: React.FC = ({ @@ -92,6 +94,7 @@ const AlertsTreemapPanelComponent: React.FC = ({ showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status, + extraActions, }: Props) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -237,6 +240,7 @@ const AlertsTreemapPanelComponent: React.FC = ({ timerange={timerange} scopeId={SourcererScopeName.detections} alertsOptions={alertsOptions} + extraActions={extraActions} /> ) : isLoadingAlerts ? ( diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index eb2e8a2513468d..359d124102afe8 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -58,6 +58,7 @@ const LensEmbeddableComponent: React.FC = ({ timerange, inspectTitle, alertsOptions, + extraActions, }) => { const { lens } = useKibana().services; const dispatch = useDispatch(); @@ -90,6 +91,7 @@ const LensEmbeddableComponent: React.FC = ({ attributes, timeRange: timerange, inspectActionProps, + extraActions, }); const handleCloseModal = useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index f22af1f0d3bc8c..e18389d1448e92 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -6,6 +6,8 @@ */ import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import type { Action } from '@kbn/ui-actions-plugin/public'; + import type { Status } from '../../../../common/detection_engine/schemas/common'; import type { InputsModelId } from '../../store/inputs/constants'; import type { SourcererScopeName } from '../../store/sourcerer/model'; @@ -42,6 +44,7 @@ export interface LensEmbeddableComponentProps { stackByField?: string; timerange: { from: string; to: string }; alertsOptions?: AlertsOptions; + extraActions?: Action[]; } export enum RequestStatus { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts index a1761682e9e725..5cd1b01ef7dfb1 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts @@ -22,6 +22,7 @@ export const useActions = ({ attributes, timeRange, inspectActionProps, + extraActions, }: { withActions?: boolean; @@ -30,6 +31,8 @@ export const useActions = ({ timeRange: { from: string; to: string }; inspectActionProps?: { onInspectActionClicked: () => void; isDisabled: boolean }; + + extraActions?: Action[]; }) => { const { lens } = useKibana().services; const { navigateToPrefilledEditor } = lens; @@ -120,7 +123,9 @@ export const useActions = ({ ] ); - return actions; + const withExtraActions = actions.concat(extraActions ?? []); + + return withExtraActions; }; const getOpenInLensAction = ({ callback }: { callback: () => void }): Action => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index bf2a79499331eb..1f402165be7bc0 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -6,6 +6,7 @@ */ import type { EuiComboBox } from '@elastic/eui'; +import type { Action } from '@kbn/ui-actions-plugin/public'; import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; import React, { memo, useMemo, useState, useEffect, useCallback } from 'react'; import uuid from 'uuid'; @@ -59,6 +60,7 @@ interface AlertsCountPanelProps { showBuildingBlockAlerts: boolean; status: Status; showOnlyThreatIndicatorAlerts: boolean; + extraActions?: Action[]; } const ChartHeight = '180px'; @@ -85,6 +87,7 @@ export const AlertsCountPanel = memo( showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status, + extraActions, }) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -238,6 +241,7 @@ export const AlertsCountPanel = memo( timerange={timerange} scopeId={SourcererScopeName.detections} alertsOptions={alertsOptions} + extraActions={extraActions} /> ) : alertsData != null ? ( ( showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status, + extraActions, }) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -445,6 +448,7 @@ export const AlertsHistogramPanel = memo( timerange={timerange} scopeId={SourcererScopeName.detections} alertsOptions={alertsOptions} + extraActions={extraActions} /> ) : isInitialLoading ? ( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx index 50caf3fb661f73..565123d5e49995 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx @@ -6,6 +6,7 @@ */ import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import type { Filter, Query } from '@kbn/es-query'; import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; @@ -29,6 +30,7 @@ import { import { AlertsCountPanel } from '../../../components/alerts_kpis/alerts_count_panel'; import { GROUP_BY_LABEL } from '../../../components/alerts_kpis/common/translations'; import type { Status } from '../../../../../common/detection_engine/schemas/common'; +import { RESET_GROUP_BY_FIELDS } from '../../../../common/components/chart_settings_popover/configurations/default/translations'; const TABLE_PANEL_HEIGHT = 330; // px const TRENT_CHART_HEIGHT = 127; // px @@ -119,6 +121,35 @@ const ChartPanelsComponent: React.FC = ({ onResetStackByField1(); }, [onResetStackByField0, onResetStackByField1]); + const resetGroupByFieldAction = useMemo( + () => [ + { + id: 'resetGroupByField', + + getDisplayName(context: ActionExecutionContext): string { + return RESET_GROUP_BY_FIELDS; + }, + getIconType(context: ActionExecutionContext): string | undefined { + return 'editorRedo'; + }, + type: 'actionButton', + async isCompatible(context: ActionExecutionContext): Promise { + return true; + }, + async execute(context: ActionExecutionContext): Promise { + onReset(); + updateCommonStackBy0(DEFAULT_STACK_BY_FIELD); + + if (updateCommonStackBy1 != null) { + updateCommonStackBy1(DEFAULT_STACK_BY_FIELD1); + } + }, + order: 0, + }, + ], + [onReset, updateCommonStackBy0, updateCommonStackBy1] + ); + const chartOptionsContextMenu = useCallback( (queryId: string) => ( = ({ showBuildingBlockAlerts={showBuildingBlockAlerts} status={filterGroup} showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} + extraActions={resetGroupByFieldAction} /> )} @@ -208,6 +240,7 @@ const ChartPanelsComponent: React.FC = ({ showBuildingBlockAlerts={showBuildingBlockAlerts} status={filterGroup} showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} + extraActions={resetGroupByFieldAction} /> )} @@ -242,6 +275,7 @@ const ChartPanelsComponent: React.FC = ({ showBuildingBlockAlerts={showBuildingBlockAlerts} status={filterGroup} showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} + extraActions={resetGroupByFieldAction} /> )} From 73424d7d76f5f4cb3725eab90cc3fb3a9c7098a4 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 21 Dec 2022 17:01:40 +0000 Subject: [PATCH 06/72] add no data prompt --- .../visualization_actions/lens_embeddable.tsx | 13 ++++++++++++- .../visualization_actions/use_lens_attributes.tsx | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 359d124102afe8..7105f852bd60ca 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -8,8 +8,10 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ViewMode } from '@kbn/embeddable-plugin/public'; import styled from 'styled-components'; +import { EuiEmptyPrompt } from '@elastic/eui'; import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { useKibana } from '../../lib/kibana'; import { useLensAttributes } from './use_lens_attributes'; @@ -152,7 +154,16 @@ const LensEmbeddableComponent: React.FC = ({ showInspector={false} /> - ) : null} + ) : ( + + } + /> + )} {isShowingModal && requests.request !== null && responses.response !== null && ( { - const { selectedPatterns, dataViewId } = useSourcererDataView(scopeId); + const { selectedPatterns, dataViewId, indicesExist } = useSourcererDataView(scopeId); const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); const getGlobalFiltersQuerySelector = useMemo( () => inputsSelectors.globalFiltersQuerySelector(), @@ -111,5 +111,5 @@ export const useLensAttributes = ({ dataViewId, ]); - return lensAttrsWithInjectedData; + return indicesExist ? lensAttrsWithInjectedData : null; }; From 5fc4eceee7a707efd3b73b687dd6ed11a5001506 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 21 Dec 2022 17:36:39 +0000 Subject: [PATCH 07/72] fix types --- .../common/components/alerts_treemap_panel/index.test.tsx | 4 ++++ .../components/alerts_kpis/alerts_count_panel/index.test.tsx | 5 +++++ .../public/detections/components/alerts_kpis/common/hooks.ts | 2 +- .../pages/detection_engine/chart_panels/index.test.tsx | 4 ++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx index 0db21ee27ea75d..9c7d731bec5a0b 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx @@ -9,6 +9,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import React from 'react'; import { useLocation } from 'react-router-dom'; +import type { Status } from '../../../../common/detection_engine/schemas/common'; import { SecurityPageName } from '../../../../common/constants'; import { useGlobalTime } from '../../containers/use_global_time'; import { @@ -121,6 +122,9 @@ const defaultProps: Props = { stackByField0: 'kibana.alert.rule.name', stackByField1: 'host.name', title: , + showBuildingBlockAlerts: false, + status: 'open' as Status, + showOnlyThreatIndicatorAlerts: false, }; describe('AlertsTreemapPanel', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx index fc752b243f9b1c..536e5a77649113 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx @@ -10,6 +10,8 @@ import { waitFor, act } from '@testing-library/react'; import { mount } from 'enzyme'; import { AlertsCountPanel } from '.'; + +import type { Status } from '../../../../../common/detection_engine/schemas/common'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { DEFAULT_STACK_BY_FIELD, DEFAULT_STACK_BY_FIELD1 } from '../common/config'; @@ -58,6 +60,9 @@ describe('AlertsCountPanel', () => { stackByField1: DEFAULT_STACK_BY_FIELD1, setStackByField0: jest.fn(), setStackByField1: jest.fn(), + showBuildingBlockAlerts: false, + showOnlyThreatIndicatorAlerts: false, + status: 'open' as Status, }; const mockSetToggle = jest.fn(); const mockUseQueryToggle = useQueryToggle as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts index b5daf49cdd9904..079b69b76e4885 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts @@ -19,7 +19,7 @@ export interface UseInspectButtonParams extends Pick void) | null; uniqueQueryId: string; loading: boolean; - searchSessionId: string; + searchSessionId?: string; } /** diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx index 4cef9f95dcc6e8..127ebb95a0ba5e 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx @@ -9,6 +9,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import React from 'react'; import { useAlertsLocalStorage } from './alerts_local_storage'; +import type { Status } from '../../../../../common/detection_engine/schemas/common'; import { RESET_GROUP_BY_FIELDS } from '../../../../common/components/chart_settings_popover/configurations/default/translations'; import { CHART_SETTINGS_POPOVER_ARIA_LABEL } from '../../../../common/components/chart_settings_popover/translations'; import { mockBrowserFields } from '../../../../common/containers/source/mock'; @@ -112,6 +113,9 @@ const defaultProps = { runtimeMappings: {}, signalIndexName: '.alerts-security.alerts-default', updateDateRangeCallback: jest.fn(), + showBuildingBlockAlerts: false, + filterGroup: 'open' as Status, + showOnlyThreatIndicatorAlerts: false, }; const resetGroupByFields = () => { From 761998f7763fd0558413479b39901608a56a36ab Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 22 Dec 2022 11:05:57 +0000 Subject: [PATCH 08/72] fix unit tests --- .../events_tab/events_query_tab_body.test.tsx | 3 + .../matrix_histogram/index.test.tsx | 101 +----------------- .../visualization_actions/__mocks__/index.tsx | 11 ++ .../__mocks__/lens_embeddable.tsx | 9 ++ .../common/__snapshots__/event.test.ts.snap | 2 +- .../common/authentication.test.ts | 1 + .../lens_attributes/common/event.test.ts | 1 + .../common/external_alert.test.ts | 1 + .../hosts/kpi_host_area.test.ts | 1 + .../hosts/kpi_host_metric.test.ts | 1 + .../hosts/kpi_unique_ips_area.test.ts | 1 + .../hosts/kpi_unique_ips_bar.test.ts | 1 + .../kpi_unique_ips_destination_metric.test.ts | 1 + .../kpi_unique_ips_source_metric.test.ts | 1 + .../network/dns_top_domains.test.ts | 1 + .../network/kpi_dns_queries.test.ts | 1 + .../network/kpi_network_events.test.ts | 1 + .../network/kpi_tls_handshakes.test.ts | 1 + .../network/kpi_unique_flow_ids.test.ts | 1 + .../kpi_unique_private_ips_area.test.ts | 1 + .../kpi_unique_private_ips_bar.test.ts | 1 + ...que_private_ips_destination_metric.test.ts | 1 + ...i_unique_private_ips_source_metric.test.ts | 1 + .../users/kpi_total_users_area.test.ts | 1 + .../users/kpi_total_users_metric.test.ts | 1 + ...user_authentication_metric_failure.test.ts | 1 + .../kpi_user_authentications_area.test.ts | 1 + .../kpi_user_authentications_bar.test.ts | 1 + ...ser_authentications_metric_success.test.ts | 1 + .../use_lens_attributes.test.tsx | 35 ++++-- .../authentications_query_tab_body.test.tsx | 3 + .../public/overview/pages/overview.test.tsx | 2 + 32 files changed, 83 insertions(+), 107 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/index.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/lens_embeddable.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx index 6737fdf2c525c7..652d8ddb8deee7 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx @@ -47,6 +47,9 @@ jest.mock('../../lib/kibana', () => { }; }); +jest.mock('../visualization_actions'); +jest.mock('../visualization_actions/lens_embeddable'); + jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useHistory: () => mockHistory, diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx index 61efd3c9065430..1b136c1b0c4640 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx @@ -15,7 +15,6 @@ import { MatrixHistogramType } from '../../../../common/search_strategy/security import { TestProviders } from '../../mock'; import { mockRuntimeMappings } from '../../containers/source/mock'; import { dnsTopDomainsLensAttributes } from '../visualization_actions/lens_attributes/network/dns_top_domains'; -import { useRouteSpy } from '../../utils/route/use_route_spy'; import { useQueryToggle } from '../../containers/query_toggle'; jest.mock('../../containers/query_toggle'); @@ -176,15 +175,7 @@ describe('Matrix Histogram Component', () => { }); describe('Inspect button', () => { - test("it doesn't render Inspect button by default on Host page", () => { - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: 'mockHost', - pageName: 'hosts', - tabName: 'events', - }, - ]); - + test("it doesn't render Inspect button by default", () => { const testProps = { ...mockMatrixOverTimeHistogramProps, lensAttributes: dnsTopDomainsLensAttributes, @@ -194,56 +185,10 @@ describe('Matrix Histogram Component', () => { }); expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(false); }); - - test("it doesn't render Inspect button by default on Network page", () => { - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: undefined, - pageName: 'network', - tabName: 'external-alerts', - }, - ]); - - const testProps = { - ...mockMatrixOverTimeHistogramProps, - lensAttributes: dnsTopDomainsLensAttributes, - }; - wrapper = mount(, { - wrappingComponent: TestProviders, - }); - expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(false); - }); - - test('it render Inspect button by default on other pages', () => { - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: undefined, - pageName: 'overview', - tabName: undefined, - }, - ]); - - const testProps = { - ...mockMatrixOverTimeHistogramProps, - lensAttributes: dnsTopDomainsLensAttributes, - }; - wrapper = mount(, { - wrappingComponent: TestProviders, - }); - expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(true); - }); }); describe('VisualizationActions', () => { - test('it renders VisualizationActions on Host page if lensAttributes is provided', () => { - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: 'mockHost', - pageName: 'hosts', - tabName: 'events', - }, - ]); - + test('it renders VisualizationActions if lensAttributes is provided', () => { const testProps = { ...mockMatrixOverTimeHistogramProps, lensAttributes: dnsTopDomainsLensAttributes, @@ -256,48 +201,6 @@ describe('Matrix Histogram Component', () => { 'histogram-viz-actions' ); }); - - test('it renders VisualizationActions on Network page if lensAttributes is provided', () => { - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: undefined, - pageName: 'network', - tabName: 'events', - }, - ]); - - const testProps = { - ...mockMatrixOverTimeHistogramProps, - lensAttributes: dnsTopDomainsLensAttributes, - }; - wrapper = mount(, { - wrappingComponent: TestProviders, - }); - expect(wrapper.find('[data-test-subj="mock-viz-actions"]').exists()).toBe(true); - expect(wrapper.find('[data-test-subj="mock-viz-actions"]').prop('className')).toEqual( - 'histogram-viz-actions' - ); - }); - - test("it doesn't renders VisualizationActions except Host / Network pages", () => { - const testProps = { - ...mockMatrixOverTimeHistogramProps, - lensAttributes: dnsTopDomainsLensAttributes, - }; - - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: undefined, - pageName: 'overview', - tabName: undefined, - }, - ]); - - wrapper = mount(, { - wrappingComponent: TestProviders, - }); - expect(wrapper.find('[data-test-subj="mock-viz-actions"]').exists()).toBe(false); - }); }); describe('toggle query', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/index.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/index.tsx new file mode 100644 index 00000000000000..fe422898ca3bf9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/index.tsx @@ -0,0 +1,11 @@ +/* + * 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 from 'react'; + +export const VisualizationActions = () =>
; + +export const HISTOGRAM_ACTIONS_BUTTON_CLASS = 'histogram-actions-trigger'; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/lens_embeddable.tsx new file mode 100644 index 00000000000000..20d5c7e8cac9ff --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/lens_embeddable.tsx @@ -0,0 +1,9 @@ +/* + * 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 from 'react'; + +export const LensEmbeddable = () =>
; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap index 7c768cc9e4b3c3..7ac274b7db3622 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap @@ -32,7 +32,7 @@ Object { "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944", "type": "column", }, - "orderDirection": "asc", + "orderDirection": "desc", "otherBucket": true, "parentFormat": Object { "id": "terms", diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts index 808789db397e9c..4a5e871bcf07c0 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts index 107b63716f4047..87d246fc2350b6 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts index da890c4d49fe05..575960076dda4f 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts index 52484448729428..5f40d582d08f80 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts index 06a884c15e4d63..04049016befb7a 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts index 63f50b141b5b0c..d7608d90e2a5cb 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts index f04f0de2b8be71..fb9d56bfd77aac 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts index 9dd67a2d5ab400..af0a079f4592d3 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts index 6e69495e63a0eb..07a9bed7e90f96 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts index a726a44e34e394..87237cf2675dad 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts index b19b5c1f2f1bac..33ea4dd0027b02 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts index 4ca26f222021ab..a335057621a0ac 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts index f06f478ca0e2be..c00e82fadf3bcf 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts index 64b9be02a1d18a..54cf848b95881e 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts index 8bb98ddaf95cfe..85fc74ca516320 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts index 894144d383b58d..557965cccaae40 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts index 7d65e042554b35..3b7eede95e3edc 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts index 88042d86632508..265d1d00bf1875 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts index 43cb5be3a97355..e4a0ecf87aa822 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts index 0a5ec9891d26f9..b35b1d6b43b15b 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts index e251ccf51c5e51..25ef5b1eabfcd9 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts index b51d3e474a7053..87a1270ec52315 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts index 41590d330cd450..1c02989f94d5a5 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts index 570e03325ec341..be232d12247276 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx index 76e2f54c62ceac..8062154ef3ce56 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx @@ -12,14 +12,9 @@ import { useLensAttributes } from './use_lens_attributes'; import { hostNameExistsFilter, getHostDetailsPageFilter, getIndexFilters } from './utils'; import { filterFromSearchBar, queryFromSearchBar, wrapper } from './mocks'; +import { useSourcererDataView } from '../../containers/sourcerer'; -jest.mock('../../containers/sourcerer', () => ({ - useSourcererDataView: jest.fn().mockReturnValue({ - selectedPatterns: ['auditbeat-*'], - dataViewId: 'security-solution-default', - }), -})); - +jest.mock('../../containers/sourcerer'); jest.mock('../../utils/route/use_route_spy', () => ({ useRouteSpy: jest.fn().mockReturnValue([ { @@ -31,6 +26,14 @@ jest.mock('../../utils/route/use_route_spy', () => ({ })); describe('useLensAttributes', () => { + beforeEach(() => { + (useSourcererDataView as jest.Mock).mockReturnValue({ + selectedPatterns: ['auditbeat-*'], + dataViewId: 'security-solution-default', + indicesExist: true, + }); + }); + it('should add query', () => { const { result } = renderHook( () => @@ -96,4 +99,22 @@ describe('useLensAttributes', () => { }, ]); }); + + it('should return null if no indices exist', () => { + (useSourcererDataView as jest.Mock).mockReturnValue({ + selectedPatterns: ['auditbeat-*'], + dataViewId: 'security-solution-default', + indicesExist: false, + }); + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getExternalAlertLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toBeNull(); + }); }); diff --git a/x-pack/plugins/security_solution/public/explore/users/pages/navigation/authentications_query_tab_body.test.tsx b/x-pack/plugins/security_solution/public/explore/users/pages/navigation/authentications_query_tab_body.test.tsx index 5a62bce6b9c3af..7d860e5a99611b 100644 --- a/x-pack/plugins/security_solution/public/explore/users/pages/navigation/authentications_query_tab_body.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/pages/navigation/authentications_query_tab_body.test.tsx @@ -17,6 +17,9 @@ jest.mock('../../../containers/authentications'); jest.mock('../../../../common/containers/query_toggle'); jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../../common/components/visualization_actions'); +jest.mock('../../../../common/components/visualization_actions/lens_embeddable'); + describe('Authentications query tab body', () => { const mockUseAuthentications = useAuthentications as jest.Mock; const mockUseQueryToggle = useQueryToggle as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx index 8f6f0e7ce11f62..3197066aeab492 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx @@ -50,6 +50,8 @@ jest.mock('../../common/lib/kibana', () => { }); jest.mock('../../common/containers/source'); jest.mock('../../common/containers/sourcerer'); +jest.mock('../../common/components/visualization_actions'); +jest.mock('../../common/components/visualization_actions/lens_embeddable'); jest.mock('../../common/containers/use_global_time', () => ({ useGlobalTime: jest.fn().mockReturnValue({ from: '2020-07-07T08:20:18.966Z', From 445cb997a9004fb83c1d009fe3f44bcea0ebadd1 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 22 Dec 2022 11:44:13 +0000 Subject: [PATCH 09/72] clean up --- .../alerts_treemap_panel/index.test.tsx | 6 +-- .../components/alerts_treemap_panel/index.tsx | 38 +++++++------- .../visualization_actions/lens_embeddable.tsx | 18 +++---- .../components/visualization_actions/types.ts | 4 +- .../use_lens_attributes.test.tsx | 4 +- .../use_lens_attributes.tsx | 14 ++--- .../pages/rule_details/index.tsx | 8 +-- .../alerts_count_panel/index.test.tsx | 6 +-- .../alerts_kpis/alerts_count_panel/index.tsx | 36 ++++++------- .../alerts_histogram_panel/index.test.tsx | 4 +- .../alerts_histogram_panel/index.tsx | 52 +++++++++---------- .../chart_panels/index.test.tsx | 4 +- .../detection_engine/chart_panels/index.tsx | 44 ++++++++-------- .../detection_engine/detection_engine.tsx | 6 +-- .../signals_by_category.tsx | 6 +-- 15 files changed, 125 insertions(+), 125 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx index 9c7d731bec5a0b..695f2c31608b4a 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx @@ -118,13 +118,13 @@ const defaultProps: Props = { setIsPanelExpanded: jest.fn(), setStackByField0: jest.fn(), setStackByField1: jest.fn(), + showBuildingBlockAlerts: false, + showOnlyThreatIndicatorAlerts: false, signalIndexName: '.alerts-security.alerts-default', stackByField0: 'kibana.alert.rule.name', stackByField1: 'host.name', - title: , - showBuildingBlockAlerts: false, status: 'open' as Status, - showOnlyThreatIndicatorAlerts: false, + title: , }; describe('AlertsTreemapPanel', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index ac365aa8a55ef1..49a0befae7bcce 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -43,10 +43,11 @@ export interface Props { addFilter?: ({ field, value }: { field: string; value: string | number }) => void; alignHeader?: 'center' | 'baseline' | 'stretch' | 'flexStart' | 'flexEnd'; chartOptionsContextMenu?: (queryId: string) => React.ReactNode; - inspectTitle: string; - isPanelExpanded: boolean; + extraActions?: Action[]; filters?: Filter[]; height?: number; + inspectTitle: string; + isPanelExpanded: boolean; query?: Query; riskSubAggregationField: string; runtimeMappings?: MappingRuntimeFields; @@ -55,27 +56,27 @@ export interface Props { setStackByField0ComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; setStackByField1: (stackBy: string | undefined) => void; setStackByField1ComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; + showBuildingBlockAlerts: boolean; + showOnlyThreatIndicatorAlerts: boolean; signalIndexName: string | null; stackByField0: string; stackByField0ComboboxRef?: React.RefObject>; stackByField1: string | undefined; stackByField1ComboboxRef?: React.RefObject>; stackByWidth?: number; - title: React.ReactNode; - showBuildingBlockAlerts: boolean; status: Status; - showOnlyThreatIndicatorAlerts: boolean; - extraActions?: Action[]; + title: React.ReactNode; } const AlertsTreemapPanelComponent: React.FC = ({ addFilter, alignHeader, chartOptionsContextMenu, - inspectTitle, - isPanelExpanded, + extraActions, filters, height = DEFAULT_HEIGHT, + inspectTitle, + isPanelExpanded, query, riskSubAggregationField, runtimeMappings, @@ -84,17 +85,16 @@ const AlertsTreemapPanelComponent: React.FC = ({ setStackByField0ComboboxInputRef, setStackByField1, setStackByField1ComboboxInputRef, + showBuildingBlockAlerts, + showOnlyThreatIndicatorAlerts, signalIndexName, stackByField0, stackByField0ComboboxRef, stackByField1, stackByField1ComboboxRef, stackByWidth, - title, - showBuildingBlockAlerts, - showOnlyThreatIndicatorAlerts, status, - extraActions, + title, }: Props) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -168,10 +168,10 @@ const AlertsTreemapPanelComponent: React.FC = ({ }); const alertsOptions = useMemo( () => ({ + breakdownField: stackByField1, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status, - breakdownField: stackByField1, }), [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status, stackByField1] ); @@ -179,12 +179,12 @@ const AlertsTreemapPanelComponent: React.FC = ({ useInspectButton({ deleteQuery, loading: isLoadingAlerts, - response, - setQuery, refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, request, - uniqueQueryId, + response, searchSessionId, + setQuery, + uniqueQueryId, }); return ( @@ -231,16 +231,16 @@ const AlertsTreemapPanelComponent: React.FC = ({ {isPanelExpanded ? ( isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( ) : isLoadingAlerts ? ( diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 7105f852bd60ca..d2c94c3d4136ef 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -50,17 +50,17 @@ const initVisualizationData: { const style = { height: '100%', minWidth: '100px' }; const LensEmbeddableComponent: React.FC = ({ + alertsOptions, + extraActions, getLensAttributes, height: wrapperHeight, id, inputsModelId = InputsModelId.global, + inspectTitle, lensAttributes, scopeId = SourcererScopeName.default, stackByField, timerange, - inspectTitle, - alertsOptions, - extraActions, }) => { const { lens } = useKibana().services; const dispatch = useDispatch(); @@ -69,12 +69,12 @@ const LensEmbeddableComponent: React.FC = ({ const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); const { searchSessionId } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); const attributes = useLensAttributes({ - lensAttributes, + alertsOptions, getLensAttributes, + lensAttributes, + scopeId, stackByField, title: '', - scopeId, - alertsOptions, }); const LensComponent = lens.EmbeddableComponent; @@ -89,11 +89,11 @@ const LensEmbeddableComponent: React.FC = ({ ); const actions = useActions({ - withActions: true, attributes, - timeRange: timerange, - inspectActionProps, extraActions, + inspectActionProps, + timeRange: timerange, + withActions: true, }); const handleCloseModal = useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index e18389d1448e92..5d728f585c4821 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -34,6 +34,8 @@ export interface VisualizationActionsProps { } export interface LensEmbeddableComponentProps { + alertsOptions?: AlertsOptions; + extraActions?: Action[]; getLensAttributes?: GetLensAttributes; height?: string; id: string; @@ -43,8 +45,6 @@ export interface LensEmbeddableComponentProps { scopeId?: SourcererScopeName; stackByField?: string; timerange: { from: string; to: string }; - alertsOptions?: AlertsOptions; - extraActions?: Action[]; } export enum RequestStatus { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx index 8062154ef3ce56..7a84fc779299c8 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx @@ -28,9 +28,9 @@ jest.mock('../../utils/route/use_route_spy', () => ({ describe('useLensAttributes', () => { beforeEach(() => { (useSourcererDataView as jest.Mock).mockReturnValue({ - selectedPatterns: ['auditbeat-*'], dataViewId: 'security-solution-default', indicesExist: true, + selectedPatterns: ['auditbeat-*'], }); }); @@ -102,9 +102,9 @@ describe('useLensAttributes', () => { it('should return null if no indices exist', () => { (useSourcererDataView as jest.Mock).mockReturnValue({ - selectedPatterns: ['auditbeat-*'], dataViewId: 'security-solution-default', indicesExist: false, + selectedPatterns: ['auditbeat-*'], }); const { result } = renderHook( () => diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index 2fe6b21de31b1a..cd402444dc769c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -98,17 +98,17 @@ export const useLensAttributes = ({ })), } as LensAttributes; }, [ - lensAttributes, - getLensAttributes, - stackByField, alertsOptions, - title, - query, + dataViewId, filters, + getLensAttributes, + indexFilters, + lensAttributes, pageFilters, + query, + stackByField, tabsFilters, - indexFilters, - dataViewId, + title, ]); return indicesExist ? lensAttrsWithInjectedData : null; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index ed600ed2b1962d..acc8f930ebe7ac 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -834,15 +834,15 @@ const RuleDetailsPageComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx index 536e5a77649113..1d38fc7cf8a009 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx @@ -55,13 +55,13 @@ jest.mock('../../../containers/detection_engine/alerts/use_query', () => { describe('AlertsCountPanel', () => { const defaultProps = { inspectTitle: TABLE, - signalIndexName: 'signalIndexName', - stackByField0: DEFAULT_STACK_BY_FIELD, - stackByField1: DEFAULT_STACK_BY_FIELD1, setStackByField0: jest.fn(), setStackByField1: jest.fn(), showBuildingBlockAlerts: false, showOnlyThreatIndicatorAlerts: false, + signalIndexName: 'signalIndexName', + stackByField0: DEFAULT_STACK_BY_FIELD, + stackByField1: DEFAULT_STACK_BY_FIELD1, status: 'open' as Status, }; const mockSetToggle = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index 1f402165be7bc0..3d83b1de77923b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -41,26 +41,26 @@ export const DETECTIONS_ALERTS_COUNT_ID = 'detections-alerts-count'; interface AlertsCountPanelProps { alignHeader?: 'center' | 'baseline' | 'stretch' | 'flexStart' | 'flexEnd'; chartOptionsContextMenu?: (queryId: string) => React.ReactNode; + extraActions?: Action[]; filters?: Filter[]; inspectTitle: string; panelHeight?: number; query?: Query; + runtimeMappings?: MappingRuntimeFields; setStackByField0: (stackBy: string) => void; setStackByField0ComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; setStackByField1: (stackBy: string | undefined) => void; setStackByField1ComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; + showBuildingBlockAlerts: boolean; + showOnlyThreatIndicatorAlerts: boolean; signalIndexName: string | null; stackByField0: string; stackByField0ComboboxRef?: React.RefObject>; stackByField1: string | undefined; stackByField1ComboboxRef?: React.RefObject>; stackByWidth?: number; - title?: React.ReactNode; - runtimeMappings?: MappingRuntimeFields; - showBuildingBlockAlerts: boolean; status: Status; - showOnlyThreatIndicatorAlerts: boolean; - extraActions?: Action[]; + title?: React.ReactNode; } const ChartHeight = '180px'; @@ -68,6 +68,7 @@ export const AlertsCountPanel = memo( ({ alignHeader, chartOptionsContextMenu, + extraActions, filters, inspectTitle, panelHeight, @@ -77,17 +78,16 @@ export const AlertsCountPanel = memo( setStackByField0ComboboxInputRef, setStackByField1, setStackByField1ComboboxInputRef, + showBuildingBlockAlerts, + showOnlyThreatIndicatorAlerts, signalIndexName, stackByField0, stackByField0ComboboxRef, stackByField1, stackByField1ComboboxRef, stackByWidth, - title = i18n.COUNT_TABLE_TITLE, - showBuildingBlockAlerts, - showOnlyThreatIndicatorAlerts, status, - extraActions, + title = i18n.COUNT_TABLE_TITLE, }) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -133,10 +133,10 @@ export const AlertsCountPanel = memo( }); const alertsOptions = useMemo( () => ({ + breakdownField: stackByField1, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status, - breakdownField: stackByField1, }), [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, stackByField1, status] ); @@ -183,14 +183,14 @@ export const AlertsCountPanel = memo( ]); useInspectButton({ - setQuery, - response, - request, - refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, - uniqueQueryId, deleteQuery, loading: isLoadingAlerts, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + request, + response, searchSessionId, + setQuery, + uniqueQueryId, }); return ( @@ -232,16 +232,16 @@ export const AlertsCountPanel = memo( {toggleStatus ? ( isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( ) : alertsData != null ? ( { describe('AlertsHistogramPanel', () => { const defaultProps = { - signalIndexName: 'signalIndexName', setQuery: jest.fn(), - updateDateRange: jest.fn(), showBuildingBlockAlerts: false, showOnlyThreatIndicatorAlerts: false, + signalIndexName: 'signalIndexName', + updateDateRange: jest.fn(), }; const mockSetToggle = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index 1899acd6722158..dfd967a1d6080b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -85,35 +85,35 @@ interface AlertsHistogramPanelProps { combinedQueries?: string; comboboxRef?: React.RefObject>; defaultStackByOption?: string; + extraActions?: Action[]; filters?: Filter[]; headerChildren?: React.ReactNode; inspectTitle?: string; + legendPosition?: Position; onFieldSelected?: (field: string) => void; /** Override all defaults, and only display this field */ onlyField?: AlertsStackByField; paddingSize?: 's' | 'm' | 'l' | 'none'; panelHeight?: number; - titleSize?: EuiTitleSize; query?: Query; - legendPosition?: Position; + runtimeMappings?: MappingRuntimeFields; setComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; - signalIndexName: string | null; + showBuildingBlockAlerts: boolean; showCountsInLegend?: boolean; showGroupByPlaceholder?: boolean; showLegend?: boolean; showLinkToAlerts?: boolean; - showTotalAlertsCount?: boolean; + showOnlyThreatIndicatorAlerts: boolean; showStackBy?: boolean; + showTotalAlertsCount?: boolean; + signalIndexName: string | null; stackByLabel?: string; stackByWidth?: number; + status?: Status; timelineId?: string; title?: React.ReactNode; + titleSize?: EuiTitleSize; updateDateRange: UpdateDateRange; - runtimeMappings?: MappingRuntimeFields; - showBuildingBlockAlerts: boolean; - status?: Status; - showOnlyThreatIndicatorAlerts: boolean; - extraActions?: Action[]; } const NO_LEGEND_DATA: LegendItem[] = []; @@ -126,34 +126,34 @@ export const AlertsHistogramPanel = memo( combinedQueries, comboboxRef, defaultStackByOption = DEFAULT_STACK_BY_FIELD, + extraActions, filters, headerChildren, inspectTitle, + legendPosition = 'right', onFieldSelected, onlyField, paddingSize = 'm', panelHeight = PANEL_HEIGHT, query, - legendPosition = 'right', + runtimeMappings, setComboboxInputRef, - signalIndexName, + showBuildingBlockAlerts, showCountsInLegend = false, showGroupByPlaceholder = false, showLegend = true, showLinkToAlerts = false, - showTotalAlertsCount = false, + showOnlyThreatIndicatorAlerts, showStackBy = true, + showTotalAlertsCount = false, + signalIndexName, stackByLabel, stackByWidth, + status, timelineId, title = i18n.HISTOGRAM_HEADER, - updateDateRange, titleSize = 'm', - runtimeMappings, - showBuildingBlockAlerts, - showOnlyThreatIndicatorAlerts, - status, - extraActions, + updateDateRange, }) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -292,14 +292,14 @@ export const AlertsHistogramPanel = memo( }, [isInitialLoading, isLoadingAlerts, setIsInitialLoading]); useInspectButton({ - setQuery, - response, - request, - refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, - uniqueQueryId, deleteQuery, loading: isLoadingAlerts, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + request, + response, searchSessionId, + setQuery, + uniqueQueryId, }); useEffect(() => { @@ -438,16 +438,16 @@ export const AlertsHistogramPanel = memo( {toggleStatus ? ( isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( ) : isInitialLoading ? ( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx index 127ebb95a0ba5e..1d452ce6819320 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx @@ -105,6 +105,7 @@ const defaultProps = { }, }, ], + filterGroup: 'open' as Status, isLoadingIndexPattern: false, query: { query: '', @@ -112,10 +113,9 @@ const defaultProps = { }, runtimeMappings: {}, signalIndexName: '.alerts-security.alerts-default', - updateDateRangeCallback: jest.fn(), showBuildingBlockAlerts: false, - filterGroup: 'open' as Status, showOnlyThreatIndicatorAlerts: false, + updateDateRangeCallback: jest.fn(), }; const resetGroupByFields = () => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx index 565123d5e49995..dbfa4893d35bf2 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx @@ -190,25 +190,25 @@ const ChartPanelsComponent: React.FC = ({ chartOptionsContextMenu={chartOptionsContextMenu} comboboxRef={stackByField0ComboboxRef} defaultStackByOption={trendChartStackBy} + extraActions={resetGroupByFieldAction} filters={alertsHistogramDefaultFilters} inspectTitle={i18n.TREND} - setComboboxInputRef={setStackByField0ComboboxInputRef} onFieldSelected={updateCommonStackBy0} panelHeight={TREND_CHART_PANEL_HEIGHT} query={query} + runtimeMappings={runtimeMappings} + setComboboxInputRef={setStackByField0ComboboxInputRef} + showBuildingBlockAlerts={showBuildingBlockAlerts} showCountsInLegend={true} showGroupByPlaceholder={false} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} showTotalAlertsCount={false} + signalIndexName={signalIndexName} stackByLabel={GROUP_BY_LABEL} + status={filterGroup} title={title} titleSize={'s'} - signalIndexName={signalIndexName} updateDateRange={updateDateRangeCallback} - runtimeMappings={runtimeMappings} - showBuildingBlockAlerts={showBuildingBlockAlerts} - status={filterGroup} - showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} - extraActions={resetGroupByFieldAction} /> )} @@ -222,6 +222,7 @@ const ChartPanelsComponent: React.FC = ({ = ({ runtimeMappings={runtimeMappings} setStackByField0={updateCommonStackBy0} setStackByField0ComboboxInputRef={setStackByField0ComboboxInputRef} - stackByField0ComboboxRef={stackByField0ComboboxRef} setStackByField1={updateCommonStackBy1} setStackByField1ComboboxInputRef={setStackByField1ComboboxInputRef} - stackByField1ComboboxRef={stackByField1ComboboxRef} + showBuildingBlockAlerts={showBuildingBlockAlerts} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} signalIndexName={signalIndexName} stackByField0={countTableStackBy0} + stackByField0ComboboxRef={stackByField0ComboboxRef} stackByField1={countTableStackBy1} - title={title} - showBuildingBlockAlerts={showBuildingBlockAlerts} + stackByField1ComboboxRef={stackByField1ComboboxRef} status={filterGroup} - showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} - extraActions={resetGroupByFieldAction} + title={title} /> )} @@ -255,27 +255,27 @@ const ChartPanelsComponent: React.FC = ({ addFilter={addFilter} alignHeader="flexStart" chartOptionsContextMenu={chartOptionsContextMenu} + extraActions={resetGroupByFieldAction} + filters={alertsHistogramDefaultFilters} inspectTitle={i18n.TREEMAP} isPanelExpanded={isTreemapPanelExpanded} - filters={alertsHistogramDefaultFilters} query={query} + riskSubAggregationField="kibana.alert.risk_score" + runtimeMappings={runtimeMappings} setIsPanelExpanded={setIsTreemapPanelExpanded} setStackByField0={updateCommonStackBy0} setStackByField0ComboboxInputRef={setStackByField0ComboboxInputRef} - stackByField0ComboboxRef={stackByField0ComboboxRef} setStackByField1={updateCommonStackBy1} setStackByField1ComboboxInputRef={setStackByField1ComboboxInputRef} - stackByField1ComboboxRef={stackByField1ComboboxRef} + showBuildingBlockAlerts={showBuildingBlockAlerts} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} signalIndexName={signalIndexName} stackByField0={riskChartStackBy0} + stackByField0ComboboxRef={stackByField0ComboboxRef} stackByField1={riskChartStackBy1} - title={title} - riskSubAggregationField="kibana.alert.risk_score" - runtimeMappings={runtimeMappings} - showBuildingBlockAlerts={showBuildingBlockAlerts} + stackByField1ComboboxRef={stackByField1ComboboxRef} status={filterGroup} - showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} - extraActions={resetGroupByFieldAction} + title={title} /> )} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index a2056b94473477..7ede04e9d5ee2f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -350,14 +350,14 @@ const DetectionEnginePageComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx index 83882c9e7b171e..ad8e686edb747c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx @@ -77,17 +77,17 @@ const SignalsByCategoryComponent: React.FC = ({ onlyField={onlyField} paddingSize={paddingSize} query={query} + runtimeMappings={runtimeMappings} + showBuildingBlockAlerts={false} showLegend={showLegend} showLinkToAlerts={onlyField == null ? true : false} + showOnlyThreatIndicatorAlerts={false} showStackBy={onlyField == null} showTotalAlertsCount={true} signalIndexName={signalIndexName} - runtimeMappings={runtimeMappings} title={i18n.ALERT_TREND} titleSize={onlyField == null ? 'm' : 's'} updateDateRange={updateDateRangeCallback} - showBuildingBlockAlerts={false} - showOnlyThreatIndicatorAlerts={false} /> ); }; From 26da70b0e0d6eb259120311ea7b9a51591c53a65 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 22 Dec 2022 12:12:53 +0000 Subject: [PATCH 10/72] update events chart --- .../lens_attributes/common/__snapshots__/event.test.ts.snap | 2 -- .../visualization_actions/lens_attributes/common/events.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap index 7ac274b7db3622..93bdbe2a0dca56 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap @@ -174,14 +174,12 @@ Object { }, ], "legend": Object { - "isInside": true, "isVisible": true, "position": "right", }, "preferredSeriesType": "bar_stacked", "title": "Empty XY chart", "valueLabels": "hide", - "valuesInLegend": true, "yLeftExtent": Object { "mode": "full", }, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts index 732f157ae3a19f..e48f6aa6c1a87f 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts @@ -20,7 +20,6 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( legend: { isVisible: true, position: 'right', - isInside: true, }, valueLabels: 'hide', preferredSeriesType: 'bar_stacked', @@ -47,7 +46,6 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( yLeft: false, yRight: true, }, - valuesInLegend: true, }, query: { query: '', From da58b26ef8d695c8ededfc7cf76610c32e669fc9 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 22 Dec 2022 12:13:17 +0000 Subject: [PATCH 11/72] rename props --- .../components/alerts_treemap_panel/index.tsx | 4 ++-- .../visualization_actions/lens_embeddable.tsx | 4 ++-- .../components/visualization_actions/types.ts | 2 +- .../use_lens_attributes.tsx | 16 ++++++++-------- .../alerts_kpis/alerts_count_panel/index.tsx | 4 ++-- .../alerts_kpis/alerts_histogram_panel/index.tsx | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index 49a0befae7bcce..3d586ca2eaec7e 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -166,7 +166,7 @@ const AlertsTreemapPanelComponent: React.FC = ({ inputId: InputsModelId.global, queryId: uniqueQueryId, }); - const alertsOptions = useMemo( + const extraVisualizationOptions = useMemo( () => ({ breakdownField: stackByField1, showBuildingBlockAlerts, @@ -231,9 +231,9 @@ const AlertsTreemapPanelComponent: React.FC = ({ {isPanelExpanded ? ( isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( = ({ - alertsOptions, + extraOptions, extraActions, getLensAttributes, height: wrapperHeight, @@ -69,7 +69,7 @@ const LensEmbeddableComponent: React.FC = ({ const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); const { searchSessionId } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); const attributes = useLensAttributes({ - alertsOptions, + extraOptions, getLensAttributes, lensAttributes, scopeId, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index 5d728f585c4821..1fa0ef4deb5402 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -34,7 +34,7 @@ export interface VisualizationActionsProps { } export interface LensEmbeddableComponentProps { - alertsOptions?: AlertsOptions; + extraOptions?: AlertsOptions; extraActions?: Action[]; getLensAttributes?: GetLensAttributes; height?: string; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index cd402444dc769c..4d5c84dbf54f17 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -23,19 +23,19 @@ import { } from './utils'; export const useLensAttributes = ({ - lensAttributes, + extraOptions, getLensAttributes, + lensAttributes, + scopeId = SourcererScopeName.default, stackByField, title, - scopeId = SourcererScopeName.default, - alertsOptions, }: { - lensAttributes?: LensAttributes | null; + extraOptions?: AlertsOptions; getLensAttributes?: GetLensAttributes; + lensAttributes?: LensAttributes | null; + scopeId?: SourcererScopeName; stackByField?: string; title?: string; - scopeId?: SourcererScopeName; - alertsOptions?: AlertsOptions; }): LensAttributes | null => { const { selectedPatterns, dataViewId, indicesExist } = useSourcererDataView(scopeId); const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); @@ -76,7 +76,7 @@ export const useLensAttributes = ({ lensAttributes ?? ((getLensAttributes && stackByField && - getLensAttributes(stackByField, alertsOptions)) as LensAttributes); + getLensAttributes(stackByField, extraOptions)) as LensAttributes); return { ...attrs, @@ -98,8 +98,8 @@ export const useLensAttributes = ({ })), } as LensAttributes; }, [ - alertsOptions, dataViewId, + extraOptions, filters, getLensAttributes, indexFilters, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index 3d83b1de77923b..2fa54aeccf50cd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -131,7 +131,7 @@ export const AlertsCountPanel = memo( inputId: InputsModelId.global, queryId: uniqueQueryId, }); - const alertsOptions = useMemo( + const extraVisualizationOptions = useMemo( () => ({ breakdownField: stackByField1, showBuildingBlockAlerts, @@ -232,9 +232,9 @@ export const AlertsCountPanel = memo( {toggleStatus ? ( isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( ( inputId: InputsModelId.global, queryId: uniqueQueryId, }); - const alertsOptions = useMemo( + const extraVisualizationOptions = useMemo( () => ({ showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, @@ -438,9 +438,9 @@ export const AlertsHistogramPanel = memo( {toggleStatus ? ( isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( Date: Thu, 22 Dec 2022 12:46:22 +0000 Subject: [PATCH 12/72] add unit tests --- .../alerts_histogram.test.ts.snap | 890 ++++++++++++++++++ .../common/alerts/alerts_histogram.test.ts | 101 ++ .../common/alerts/alerts_histogram.ts | 4 +- .../common/alerts/alerts_table.test.ts | 99 ++ .../common/alerts/alerts_table.ts | 12 +- .../common/alerts/alerts_treemap.test.ts | 99 ++ .../common/alerts/alerts_treemap.ts | 8 +- .../lens_attributes/common/alerts/utils.ts | 4 +- .../components/visualization_actions/types.ts | 6 +- .../use_lens_attributes.test.tsx | 2 +- .../use_lens_attributes.tsx | 4 +- 11 files changed, 1209 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap new file mode 100644 index 00000000000000..158bdf6cdff751 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap @@ -0,0 +1,890 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getAlertsHistogramLensAttributes should render with extra options - breakdownField 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "0039eb0c-9a1a-4687-ae54-0f4e239bec75": Object { + "columnOrder": Array [ + "34919782-4546-43a5-b668-06ac934d3acd", + "aac9d7d0-13a3-480a-892b-08207a787926", + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "columns": Object { + "34919782-4546-43a5-b668-06ac934d3acd": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "missingBucket": false, + "orderBy": Object { + "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "secondaryFields": Array [], + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "aac9d7d0-13a3-480a-892b-08207a787926": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "e09e0380-0740-4105-becc-0a4ca12e3944": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "layers": Array [ + Object { + "accessors": Array [ + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "layerId": "0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "layerType": "data", + "position": "top", + "seriesType": "bar_stacked", + "showGridlines": false, + "splitAccessor": "34919782-4546-43a5-b668-06ac934d3acd", + "xAccessor": "aac9d7d0-13a3-480a-892b-08207a787926", + }, + ], + "legend": Object { + "isInside": true, + "isVisible": true, + "position": "right", + }, + "preferredSeriesType": "bar_stacked", + "title": "Empty XY chart", + "valueLabels": "hide", + "valuesInLegend": true, + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "Alerts", + "visualizationType": "lnsXY", +} +`; + +exports[`getAlertsHistogramLensAttributes should render with extra options - showBuildingBlockAlerts 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "0039eb0c-9a1a-4687-ae54-0f4e239bec75": Object { + "columnOrder": Array [ + "34919782-4546-43a5-b668-06ac934d3acd", + "aac9d7d0-13a3-480a-892b-08207a787926", + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "columns": Object { + "34919782-4546-43a5-b668-06ac934d3acd": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "missingBucket": false, + "orderBy": Object { + "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "secondaryFields": Array [], + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "aac9d7d0-13a3-480a-892b-08207a787926": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "e09e0380-0740-4105-becc-0a4ca12e3944": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "layers": Array [ + Object { + "accessors": Array [ + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "layerId": "0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "layerType": "data", + "position": "top", + "seriesType": "bar_stacked", + "showGridlines": false, + "splitAccessor": "34919782-4546-43a5-b668-06ac934d3acd", + "xAccessor": "aac9d7d0-13a3-480a-892b-08207a787926", + }, + ], + "legend": Object { + "isInside": true, + "isVisible": true, + "position": "right", + }, + "preferredSeriesType": "bar_stacked", + "title": "Empty XY chart", + "valueLabels": "hide", + "valuesInLegend": true, + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "Alerts", + "visualizationType": "lnsXY", +} +`; + +exports[`getAlertsHistogramLensAttributes should render with extra options - showOnlyThreatIndicatorAlerts 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "0039eb0c-9a1a-4687-ae54-0f4e239bec75": Object { + "columnOrder": Array [ + "34919782-4546-43a5-b668-06ac934d3acd", + "aac9d7d0-13a3-480a-892b-08207a787926", + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "columns": Object { + "34919782-4546-43a5-b668-06ac934d3acd": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "missingBucket": false, + "orderBy": Object { + "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "secondaryFields": Array [], + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "aac9d7d0-13a3-480a-892b-08207a787926": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "e09e0380-0740-4105-becc-0a4ca12e3944": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "kibana.alert.rule.type", + "negate": false, + "params": Object { + "query": "threat_match", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "kibana.alert.rule.type": "threat_match", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "layers": Array [ + Object { + "accessors": Array [ + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "layerId": "0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "layerType": "data", + "position": "top", + "seriesType": "bar_stacked", + "showGridlines": false, + "splitAccessor": "34919782-4546-43a5-b668-06ac934d3acd", + "xAccessor": "aac9d7d0-13a3-480a-892b-08207a787926", + }, + ], + "legend": Object { + "isInside": true, + "isVisible": true, + "position": "right", + }, + "preferredSeriesType": "bar_stacked", + "title": "Empty XY chart", + "valueLabels": "hide", + "valuesInLegend": true, + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "Alerts", + "visualizationType": "lnsXY", +} +`; + +exports[`getAlertsHistogramLensAttributes should render with extra options - status 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "0039eb0c-9a1a-4687-ae54-0f4e239bec75": Object { + "columnOrder": Array [ + "34919782-4546-43a5-b668-06ac934d3acd", + "aac9d7d0-13a3-480a-892b-08207a787926", + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "columns": Object { + "34919782-4546-43a5-b668-06ac934d3acd": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "missingBucket": false, + "orderBy": Object { + "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "secondaryFields": Array [], + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "aac9d7d0-13a3-480a-892b-08207a787926": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "e09e0380-0740-4105-becc-0a4ca12e3944": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "kibana.alert.workflow_status", + "negate": false, + "params": Object { + "query": "open", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "kibana.alert.workflow_status": "open", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "layers": Array [ + Object { + "accessors": Array [ + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "layerId": "0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "layerType": "data", + "position": "top", + "seriesType": "bar_stacked", + "showGridlines": false, + "splitAccessor": "34919782-4546-43a5-b668-06ac934d3acd", + "xAccessor": "aac9d7d0-13a3-480a-892b-08207a787926", + }, + ], + "legend": Object { + "isInside": true, + "isVisible": true, + "position": "right", + }, + "preferredSeriesType": "bar_stacked", + "title": "Empty XY chart", + "valueLabels": "hide", + "valuesInLegend": true, + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "Alerts", + "visualizationType": "lnsXY", +} +`; + +exports[`getAlertsHistogramLensAttributes should render without extra options 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "0039eb0c-9a1a-4687-ae54-0f4e239bec75": Object { + "columnOrder": Array [ + "34919782-4546-43a5-b668-06ac934d3acd", + "aac9d7d0-13a3-480a-892b-08207a787926", + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "columns": Object { + "34919782-4546-43a5-b668-06ac934d3acd": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "missingBucket": false, + "orderBy": Object { + "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "secondaryFields": Array [], + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "aac9d7d0-13a3-480a-892b-08207a787926": Object { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": Object { + "interval": "auto", + }, + "scale": "interval", + "sourceField": "@timestamp", + }, + "e09e0380-0740-4105-becc-0a4ca12e3944": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "auditbeat-mytest-*", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "auditbeat-mytest-*", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "axisTitlesVisibilitySettings": Object { + "x": false, + "yLeft": false, + "yRight": true, + }, + "layers": Array [ + Object { + "accessors": Array [ + "e09e0380-0740-4105-becc-0a4ca12e3944", + ], + "layerId": "0039eb0c-9a1a-4687-ae54-0f4e239bec75", + "layerType": "data", + "position": "top", + "seriesType": "bar_stacked", + "showGridlines": false, + "splitAccessor": "34919782-4546-43a5-b668-06ac934d3acd", + "xAccessor": "aac9d7d0-13a3-480a-892b-08207a787926", + }, + ], + "legend": Object { + "isInside": true, + "isVisible": true, + "position": "right", + }, + "preferredSeriesType": "bar_stacked", + "title": "Empty XY chart", + "valueLabels": "hide", + "valuesInLegend": true, + "yLeftExtent": Object { + "mode": "full", + }, + "yRightExtent": Object { + "mode": "full", + }, + }, + }, + "title": "Alerts", + "visualizationType": "lnsXY", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts new file mode 100644 index 00000000000000..02a9223e801e73 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts @@ -0,0 +1,101 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../../mocks'; + +import { useLensAttributes } from '../../../use_lens_attributes'; + +import { getAlertsHistogramLensAttributes } from './alerts_histogram'; + +jest.mock('../../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + dataViewId: 'security-solution-my-test', + indicesExist: true, + selectedPatterns: ['signal-index'], + }), +})); + +jest.mock('../../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockRule', + pageName: 'rules', + tabName: 'alerts', + }, + ]), +})); + +describe('getAlertsHistogramLensAttributes', () => { + it('should render without extra options', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getAlertsHistogramLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - showBuildingBlockAlerts', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { showBuildingBlockAlerts: true }, + getLensAttributes: getAlertsHistogramLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - showOnlyThreatIndicatorAlerts', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { showOnlyThreatIndicatorAlerts: true }, + getLensAttributes: getAlertsHistogramLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - status', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { status: 'open' }, + getLensAttributes: getAlertsHistogramLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - breakdownField', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { breakdownField: 'agent.type' }, + getLensAttributes: getAlertsHistogramLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts index b38b20529a66d7..7b07e8cf5c239f 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts @@ -10,7 +10,7 @@ import { buildAlertsOptionsFilters } from './utils'; export const getAlertsHistogramLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', - alertsOptions = { + extraOptions = { showOnlyThreatIndicatorAlerts: false, showBuildingBlockAlerts: false, } @@ -58,7 +58,7 @@ export const getAlertsHistogramLensAttributes: GetLensAttributes = ( query: '', language: 'kuery', }, - filters: buildAlertsOptionsFilters(alertsOptions), + filters: buildAlertsOptionsFilters(extraOptions), datasourceStates: { formBased: { layers: { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts new file mode 100644 index 00000000000000..df6721595ef758 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts @@ -0,0 +1,99 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../../mocks'; + +import { useLensAttributes } from '../../../use_lens_attributes'; + +import { getAlertsTableLensAttributes } from './alerts_table'; + +jest.mock('../../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + dataViewId: 'security-solution-my-test', + indicesExist: true, + selectedPatterns: ['signal-index'], + }), +})); + +jest.mock('../../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + pageName: 'alerts', + }, + ]), +})); + +describe('getAlertsTableLensAttributes', () => { + it('should render without extra options', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getAlertsTableLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - showBuildingBlockAlerts', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { showBuildingBlockAlerts: true }, + getLensAttributes: getAlertsTableLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - showOnlyThreatIndicatorAlerts', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { showOnlyThreatIndicatorAlerts: true }, + getLensAttributes: getAlertsTableLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - status', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { status: 'open' }, + getLensAttributes: getAlertsTableLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - breakdownField', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { breakdownField: 'agent.type' }, + getLensAttributes: getAlertsTableLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts index c3cde0b93860ff..1fddb15ec60e8c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts @@ -9,7 +9,7 @@ import { buildAlertsOptionsFilters } from './utils'; export const getAlertsTableLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', - alertsOptions = { + extraOptions = { showOnlyThreatIndicatorAlerts: false, showBuildingBlockAlerts: false, } @@ -45,7 +45,7 @@ export const getAlertsTableLensAttributes: GetLensAttributes = ( query: '', language: 'kuery', }, - filters: buildAlertsOptionsFilters(alertsOptions), + filters: buildAlertsOptionsFilters(extraOptions), datasourceStates: { formBased: { layers: { @@ -77,22 +77,22 @@ export const getAlertsTableLensAttributes: GetLensAttributes = ( }, }, 'f04a71a3-399f-4d32-9efc-8a005e989991': { - label: `Count of ${alertsOptions.breakdownField}`, + label: `Count of ${extraOptions.breakdownField}`, dataType: 'number', operationType: 'count', isBucketed: false, scale: 'ratio', - sourceField: alertsOptions.breakdownField, + sourceField: extraOptions.breakdownField, params: { emptyAsNull: true, }, }, '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { - label: `Top values of ${alertsOptions.breakdownField}`, + label: `Top values of ${extraOptions.breakdownField}`, dataType: 'string', operationType: 'terms', scale: 'ordinal', - sourceField: alertsOptions.breakdownField, + sourceField: extraOptions.breakdownField, isBucketed: true, params: { size: 1000, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts new file mode 100644 index 00000000000000..9492d27adfa7a5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts @@ -0,0 +1,99 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../../mocks'; + +import { useLensAttributes } from '../../../use_lens_attributes'; + +import { getAlertsTreemapLensAttributes } from './alerts_treemap'; + +jest.mock('../../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + dataViewId: 'security-solution-my-test', + indicesExist: true, + selectedPatterns: ['signal-index'], + }), +})); + +jest.mock('../../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + pageName: 'alerts', + }, + ]), +})); + +describe('getAlertsTreemapLensAttributes', () => { + it('should render without extra options', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getAlertsTreemapLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - showBuildingBlockAlerts', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { showBuildingBlockAlerts: true }, + getLensAttributes: getAlertsTreemapLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - showOnlyThreatIndicatorAlerts', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { showOnlyThreatIndicatorAlerts: true }, + getLensAttributes: getAlertsTreemapLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - status', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { status: 'open' }, + getLensAttributes: getAlertsTreemapLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - breakdownField', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { breakdownField: 'agent.type' }, + getLensAttributes: getAlertsTreemapLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts index 12c45b97df0b4f..8b6fcb315f578a 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts @@ -9,7 +9,7 @@ import { buildAlertsOptionsFilters } from './utils'; export const getAlertsTreemapLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', - alertsOptions = { + extraOptions = { showOnlyThreatIndicatorAlerts: false, showBuildingBlockAlerts: false, } @@ -42,7 +42,7 @@ export const getAlertsTreemapLensAttributes: GetLensAttributes = ( query: '', language: 'kuery', }, - filters: buildAlertsOptionsFilters(alertsOptions), + filters: buildAlertsOptionsFilters(extraOptions), datasourceStates: { formBased: { layers: { @@ -74,11 +74,11 @@ export const getAlertsTreemapLensAttributes: GetLensAttributes = ( }, }, '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { - label: `Top values of ${alertsOptions.breakdownField}`, + label: `Top values of ${extraOptions.breakdownField}`, dataType: 'string', operationType: 'terms', scale: 'ordinal', - sourceField: alertsOptions.breakdownField, + sourceField: extraOptions.breakdownField, isBucketed: true, params: { size: 1000, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts index 86caecf3138e7a..99a1b2363bb1f5 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { AlertsOptions } from '../../../types'; +import type { ExtraOptions } from '../../../types'; export const buildAlertsOptionsFilters = ({ showBuildingBlockAlerts = false, showOnlyThreatIndicatorAlerts = false, status, -}: AlertsOptions) => [ +}: ExtraOptions) => [ ...(status ? [ { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index 1fa0ef4deb5402..d4b3b6dbe284cb 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -15,7 +15,7 @@ import type { SourcererScopeName } from '../../store/sourcerer/model'; export type LensAttributes = TypedLensByValueInput['attributes']; export type GetLensAttributes = ( stackByField?: string, - alertsOptions?: AlertsOptions + alertsOptions?: ExtraOptions ) => LensAttributes; export interface VisualizationActionsProps { @@ -34,7 +34,7 @@ export interface VisualizationActionsProps { } export interface LensEmbeddableComponentProps { - extraOptions?: AlertsOptions; + extraOptions?: ExtraOptions; extraActions?: Action[]; getLensAttributes?: GetLensAttributes; height?: string; @@ -85,7 +85,7 @@ export interface Response { time?: number; } -export interface AlertsOptions { +export interface ExtraOptions { showBuildingBlockAlerts?: boolean; showOnlyThreatIndicatorAlerts?: boolean; status?: Status; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx index 7a84fc779299c8..ca23d57d2c1955 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx @@ -30,7 +30,7 @@ describe('useLensAttributes', () => { (useSourcererDataView as jest.Mock).mockReturnValue({ dataViewId: 'security-solution-default', indicesExist: true, - selectedPatterns: ['auditbeat-*'], + selectedPatterns: ['signal-index'], }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index 4d5c84dbf54f17..17ece9461edbd7 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -14,7 +14,7 @@ import { useDeepEqualSelector } from '../../hooks/use_selector'; import { inputsSelectors } from '../../store'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { useRouteSpy } from '../../utils/route/use_route_spy'; -import type { LensAttributes, GetLensAttributes, AlertsOptions } from './types'; +import type { LensAttributes, GetLensAttributes, ExtraOptions } from './types'; import { getHostDetailsPageFilter, sourceOrDestinationIpExistsFilter, @@ -30,7 +30,7 @@ export const useLensAttributes = ({ stackByField, title, }: { - extraOptions?: AlertsOptions; + extraOptions?: ExtraOptions; getLensAttributes?: GetLensAttributes; lensAttributes?: LensAttributes | null; scopeId?: SourcererScopeName; From 55f0cf24484ea5543ee0d7fdf9421eca00207b97 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 22 Dec 2022 14:14:45 +0000 Subject: [PATCH 13/72] update snapshots --- .../alerts_histogram.test.ts.snap | 20 +- .../__snapshots__/alerts_table.test.ts.snap | 945 ++++++++++++++++++ .../__snapshots__/alerts_treemap.test.ts.snap | 940 +++++++++++++++++ 3 files changed, 1895 insertions(+), 10 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap index 158bdf6cdff751..dabf33958fbb37 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap @@ -109,7 +109,7 @@ Object { "key": "_index", "negate": false, "params": Array [ - "auditbeat-mytest-*", + "signal-index", ], "type": "phrases", }, @@ -119,7 +119,7 @@ Object { "should": Array [ Object { "match_phrase": Object { - "_index": "auditbeat-mytest-*", + "_index": "signal-index", }, }, ], @@ -268,7 +268,7 @@ Object { "key": "_index", "negate": false, "params": Array [ - "auditbeat-mytest-*", + "signal-index", ], "type": "phrases", }, @@ -278,7 +278,7 @@ Object { "should": Array [ Object { "match_phrase": Object { - "_index": "auditbeat-mytest-*", + "_index": "signal-index", }, }, ], @@ -459,7 +459,7 @@ Object { "key": "_index", "negate": false, "params": Array [ - "auditbeat-mytest-*", + "signal-index", ], "type": "phrases", }, @@ -469,7 +469,7 @@ Object { "should": Array [ Object { "match_phrase": Object { - "_index": "auditbeat-mytest-*", + "_index": "signal-index", }, }, ], @@ -650,7 +650,7 @@ Object { "key": "_index", "negate": false, "params": Array [ - "auditbeat-mytest-*", + "signal-index", ], "type": "phrases", }, @@ -660,7 +660,7 @@ Object { "should": Array [ Object { "match_phrase": Object { - "_index": "auditbeat-mytest-*", + "_index": "signal-index", }, }, ], @@ -824,7 +824,7 @@ Object { "key": "_index", "negate": false, "params": Array [ - "auditbeat-mytest-*", + "signal-index", ], "type": "phrases", }, @@ -834,7 +834,7 @@ Object { "should": Array [ Object { "match_phrase": Object { - "_index": "auditbeat-mytest-*", + "_index": "signal-index", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap new file mode 100644 index 00000000000000..5196780c11dcf0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap @@ -0,0 +1,945 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getAlertsTableLensAttributes should render with extra options - breakdownField 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "columnOrder": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "columns": Object { + "2881fedd-54b7-42ba-8c97-5175dec86166": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of agent.type", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "agent.type", + }, + "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of agent.type", + "operationType": "count", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": "agent.type", + }, + }, + "incompleteColumns": Object {}, + "sampling": 1, + }, + }, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "signal-index", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "signal-index", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "columns": Array [ + Object { + "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166", + "isTransposed": false, + "width": 362, + }, + Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "isTransposed": false, + }, + Object { + "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "isTransposed": false, + }, + ], + "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerType": "data", + "paging": Object { + "enabled": true, + "size": 10, + }, + }, + }, + "title": "Alerts", + "visualizationType": "lnsDatatable", +} +`; + +exports[`getAlertsTableLensAttributes should render with extra options - showBuildingBlockAlerts 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "columnOrder": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "columns": Object { + "2881fedd-54b7-42ba-8c97-5175dec86166": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of undefined", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": undefined, + }, + "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of undefined", + "operationType": "count", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": undefined, + }, + }, + "incompleteColumns": Object {}, + "sampling": 1, + }, + }, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "signal-index", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "signal-index", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "columns": Array [ + Object { + "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166", + "isTransposed": false, + "width": 362, + }, + Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "isTransposed": false, + }, + Object { + "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "isTransposed": false, + }, + ], + "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerType": "data", + "paging": Object { + "enabled": true, + "size": 10, + }, + }, + }, + "title": "Alerts", + "visualizationType": "lnsDatatable", +} +`; + +exports[`getAlertsTableLensAttributes should render with extra options - showOnlyThreatIndicatorAlerts 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "columnOrder": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "columns": Object { + "2881fedd-54b7-42ba-8c97-5175dec86166": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of undefined", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": undefined, + }, + "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of undefined", + "operationType": "count", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": undefined, + }, + }, + "incompleteColumns": Object {}, + "sampling": 1, + }, + }, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "kibana.alert.rule.type", + "negate": false, + "params": Object { + "query": "threat_match", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "kibana.alert.rule.type": "threat_match", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "signal-index", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "signal-index", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "columns": Array [ + Object { + "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166", + "isTransposed": false, + "width": 362, + }, + Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "isTransposed": false, + }, + Object { + "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "isTransposed": false, + }, + ], + "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerType": "data", + "paging": Object { + "enabled": true, + "size": 10, + }, + }, + }, + "title": "Alerts", + "visualizationType": "lnsDatatable", +} +`; + +exports[`getAlertsTableLensAttributes should render with extra options - status 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "columnOrder": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "columns": Object { + "2881fedd-54b7-42ba-8c97-5175dec86166": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of undefined", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": undefined, + }, + "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of undefined", + "operationType": "count", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": undefined, + }, + }, + "incompleteColumns": Object {}, + "sampling": 1, + }, + }, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "kibana.alert.workflow_status", + "negate": false, + "params": Object { + "query": "open", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "kibana.alert.workflow_status": "open", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "signal-index", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "signal-index", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "columns": Array [ + Object { + "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166", + "isTransposed": false, + "width": 362, + }, + Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "isTransposed": false, + }, + Object { + "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "isTransposed": false, + }, + ], + "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerType": "data", + "paging": Object { + "enabled": true, + "size": 10, + }, + }, + }, + "title": "Alerts", + "visualizationType": "lnsDatatable", +} +`; + +exports[`getAlertsTableLensAttributes should render without extra options 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "columnOrder": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "columns": Object { + "2881fedd-54b7-42ba-8c97-5175dec86166": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of undefined", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": undefined, + }, + "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of undefined", + "operationType": "count", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": undefined, + }, + }, + "incompleteColumns": Object {}, + "sampling": 1, + }, + }, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "signal-index", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "signal-index", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "columns": Array [ + Object { + "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166", + "isTransposed": false, + "width": 362, + }, + Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "isTransposed": false, + }, + Object { + "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "isTransposed": false, + }, + ], + "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerType": "data", + "paging": Object { + "enabled": true, + "size": 10, + }, + }, + }, + "title": "Alerts", + "visualizationType": "lnsDatatable", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap new file mode 100644 index 00000000000000..4a440221a9f08c --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap @@ -0,0 +1,940 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getAlertsTreemapLensAttributes should render with extra options - breakdownField 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "columnOrder": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "columns": Object { + "2881fedd-54b7-42ba-8c97-5175dec86166": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of agent.type", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "agent.type", + }, + "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "dataType": "number", + "isBucketed": false, + "label": "Maximum of kibana.alert.risk_score", + "operationType": "max", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": "kibana.alert.risk_score", + }, + }, + "incompleteColumns": Object {}, + "sampling": 1, + }, + }, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "signal-index", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "signal-index", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "layers": Array [ + Object { + "categoryDisplay": "default", + "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerType": "data", + "legendDisplay": "show", + "legendPosition": "left", + "legendSize": "xlarge", + "metrics": Array [ + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "nestedLegend": true, + "numberDisplay": "value", + "primaryGroups": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + ], + }, + ], + "shape": "treemap", + }, + }, + "title": "Alerts", + "visualizationType": "lnsPie", +} +`; + +exports[`getAlertsTreemapLensAttributes should render with extra options - showBuildingBlockAlerts 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "columnOrder": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "columns": Object { + "2881fedd-54b7-42ba-8c97-5175dec86166": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of undefined", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": undefined, + }, + "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "dataType": "number", + "isBucketed": false, + "label": "Maximum of kibana.alert.risk_score", + "operationType": "max", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": "kibana.alert.risk_score", + }, + }, + "incompleteColumns": Object {}, + "sampling": 1, + }, + }, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "signal-index", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "signal-index", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "layers": Array [ + Object { + "categoryDisplay": "default", + "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerType": "data", + "legendDisplay": "show", + "legendPosition": "left", + "legendSize": "xlarge", + "metrics": Array [ + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "nestedLegend": true, + "numberDisplay": "value", + "primaryGroups": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + ], + }, + ], + "shape": "treemap", + }, + }, + "title": "Alerts", + "visualizationType": "lnsPie", +} +`; + +exports[`getAlertsTreemapLensAttributes should render with extra options - showOnlyThreatIndicatorAlerts 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "columnOrder": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "columns": Object { + "2881fedd-54b7-42ba-8c97-5175dec86166": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of undefined", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": undefined, + }, + "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "dataType": "number", + "isBucketed": false, + "label": "Maximum of kibana.alert.risk_score", + "operationType": "max", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": "kibana.alert.risk_score", + }, + }, + "incompleteColumns": Object {}, + "sampling": 1, + }, + }, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "kibana.alert.rule.type", + "negate": false, + "params": Object { + "query": "threat_match", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "kibana.alert.rule.type": "threat_match", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "signal-index", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "signal-index", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "layers": Array [ + Object { + "categoryDisplay": "default", + "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerType": "data", + "legendDisplay": "show", + "legendPosition": "left", + "legendSize": "xlarge", + "metrics": Array [ + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "nestedLegend": true, + "numberDisplay": "value", + "primaryGroups": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + ], + }, + ], + "shape": "treemap", + }, + }, + "title": "Alerts", + "visualizationType": "lnsPie", +} +`; + +exports[`getAlertsTreemapLensAttributes should render with extra options - status 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "columnOrder": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "columns": Object { + "2881fedd-54b7-42ba-8c97-5175dec86166": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of undefined", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": undefined, + }, + "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "dataType": "number", + "isBucketed": false, + "label": "Maximum of kibana.alert.risk_score", + "operationType": "max", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": "kibana.alert.risk_score", + }, + }, + "incompleteColumns": Object {}, + "sampling": 1, + }, + }, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "kibana.alert.workflow_status", + "negate": false, + "params": Object { + "query": "open", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "kibana.alert.workflow_status": "open", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "signal-index", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "signal-index", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "layers": Array [ + Object { + "categoryDisplay": "default", + "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerType": "data", + "legendDisplay": "show", + "legendPosition": "left", + "legendSize": "xlarge", + "metrics": Array [ + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "nestedLegend": true, + "numberDisplay": "value", + "primaryGroups": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + ], + }, + ], + "shape": "treemap", + }, + }, + "title": "Alerts", + "visualizationType": "lnsPie", +} +`; + +exports[`getAlertsTreemapLensAttributes should render without extra options 1`] = ` +Object { + "description": "", + "references": Array [ + Object { + "id": "security-solution-my-test", + "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "type": "index-pattern", + }, + ], + "state": Object { + "adHocDataViews": Object {}, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "columnOrder": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "columns": Object { + "2881fedd-54b7-42ba-8c97-5175dec86166": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of event.category", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": "event.category", + }, + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top values of undefined", + "operationType": "terms", + "params": Object { + "exclude": Array [], + "excludeIsRegex": false, + "include": Array [], + "includeIsRegex": false, + "missingBucket": false, + "orderBy": Object { + "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "type": "column", + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": Object { + "id": "terms", + }, + "size": 1000, + }, + "scale": "ordinal", + "sourceField": undefined, + }, + "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "dataType": "number", + "isBucketed": false, + "label": "Maximum of kibana.alert.risk_score", + "operationType": "max", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": "kibana.alert.risk_score", + }, + }, + "incompleteColumns": Object {}, + "sampling": 1, + }, + }, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": ".alerts-security.alerts-id", + "key": "kibana.alert.building_block_type", + "negate": true, + "type": "exists", + }, + "query": Object { + "exists": Object { + "field": "kibana.alert.building_block_type", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "_index", + "negate": false, + "params": Array [ + "signal-index", + ], + "type": "phrases", + }, + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match_phrase": Object { + "_index": "signal-index", + }, + }, + ], + }, + }, + }, + ], + "internalReferences": Array [], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "layers": Array [ + Object { + "categoryDisplay": "default", + "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerType": "data", + "legendDisplay": "show", + "legendPosition": "left", + "legendSize": "xlarge", + "metrics": Array [ + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "nestedLegend": true, + "numberDisplay": "value", + "primaryGroups": Array [ + "2881fedd-54b7-42ba-8c97-5175dec86166", + "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + ], + }, + ], + "shape": "treemap", + }, + }, + "title": "Alerts", + "visualizationType": "lnsPie", +} +`; From 7dbc50d40ccddda5e2c5feeff35ee8621242a2bd Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 23 Dec 2022 14:02:52 +0000 Subject: [PATCH 14/72] add unit tests --- .../alerts_treemap_panel/index.test.tsx | 87 +++++++++++++++ .../components/alerts_treemap_panel/index.tsx | 34 +++--- .../__mocks__/lens_embeddable.tsx | 2 +- .../alerts_count_panel/index.test.tsx | 101 +++++++++++++++--- .../alerts_histogram_panel/index.test.tsx | 76 +++++++++++++ 5 files changed, 267 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx index 695f2c31608b4a..ee8e0c6b083f65 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx @@ -24,6 +24,9 @@ import { TestProviders } from '../../mock/test_providers'; import type { Props } from '.'; import { AlertsTreemapPanel } from '.'; import { mockAlertSearchResponse } from '../alerts_treemap/lib/mocks/mock_alert_search_response'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; +import { useInspectButton } from '../../../detections/components/alerts_kpis/common/hooks'; +import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; const from = '2022-07-28T08:20:18.966Z'; const to = '2022-07-28T08:20:18.966Z'; @@ -54,6 +57,21 @@ jest.mock('../../../detections/containers/detection_engine/alerts/use_query', () useQueryAlerts: jest.fn(), })); +jest.mock('../../hooks/use_experimental_features'); +jest.mock('../page/use_refetch_by_session'); +jest.mock('../visualization_actions/lens_embeddable'); + +jest.mock('../page/use_refetch_by_session', () => ({ + useRefetchByRestartingSession: jest.fn().mockReturnValue({ + searchSessionId: 'mockSearchSessionId', + refetchByRestartingSession: jest.fn(), + }), +})); +jest.mock('../../../detections/components/alerts_kpis/common/hooks', () => ({ + useInspectButton: jest.fn(), + useStackByFields: jest.fn(), +})); + const defaultProps: Props = { addFilter: jest.fn(), alignHeader: 'flexStart', @@ -143,6 +161,8 @@ describe('AlertsTreemapPanel', () => { request: '', refetch: () => {}, }); + + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); }); it('renders the panel', async () => { @@ -312,3 +332,70 @@ describe('AlertsTreemapPanel', () => { await waitFor(() => expect(screen.getByTestId('treemap')).toBeInTheDocument()); }); }); + +describe('when isChartEmbeddablesEnabled = true', () => { + const mockSearchSessionId = 'mockSearchSessionId'; + const mockRefetchByRestartingSession = jest.fn(); + const mockRefetch = () => {}; + + beforeEach(() => { + jest.clearAllMocks(); + + (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ + searchSessionId: mockSearchSessionId, + refetchByRestartingSession: mockRefetchByRestartingSession, + }); + + (useLocation as jest.Mock).mockReturnValue([ + { pageName: SecurityPageName.alerts, detailName: undefined }, + ]); + + (useQueryAlerts as jest.Mock).mockReturnValue({ + loading: false, + data: mockAlertSearchResponse, + setQuery: () => {}, + response: '', + request: '', + refetch: mockRefetch, + }); + + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + }); + + it('refetch data by refetchByRestartingSession', async () => { + render( + + + + ); + + await waitFor(() => { + expect((useInspectButton as jest.Mock).mock.calls[0][0].refetch).toEqual( + mockRefetchByRestartingSession + ); + expect((useInspectButton as jest.Mock).mock.calls[0][0].searchSessionId).toEqual( + mockSearchSessionId + ); + }); + }); + + it('renders LensEmbeddable', async () => { + render( + + + + ); + + await waitFor(() => expect(screen.getByTestId('lens-embeddable')).toBeInTheDocument()); + }); + + it('should skip calling getAlertsRiskQuery', async () => { + render( + + + + ); + + await waitFor(() => expect((useQueryAlerts as jest.Mock).mock.calls[0][0].skip).toBeTruthy()); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index 3d586ca2eaec7e..096744fd5f5452 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -100,6 +100,21 @@ const AlertsTreemapPanelComponent: React.FC = ({ // create a unique, but stable (across re-renders) query id const uniqueQueryId = useMemo(() => `${ALERTS_TREEMAP_ID}-${uuid.v4()}`, []); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const timerange = useMemo(() => ({ from, to }), [from, to]); + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: uniqueQueryId, + }); + const extraVisualizationOptions = useMemo( + () => ({ + breakdownField: stackByField1, + showBuildingBlockAlerts, + showOnlyThreatIndicatorAlerts, + status, + }), + [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status, stackByField1] + ); const additionalFilters = useMemo(() => { try { @@ -132,7 +147,7 @@ const AlertsTreemapPanelComponent: React.FC = ({ stackByField1, to, }), - skip: !isPanelExpanded, + skip: !isPanelExpanded || isChartEmbeddablesEnabled, indexName: signalIndexName, queryName: ALERTS_QUERY_NAMES.TREE_MAP, }); @@ -160,22 +175,6 @@ const AlertsTreemapPanelComponent: React.FC = ({ to, ]); - const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); - const timerange = useMemo(() => ({ from, to }), [from, to]); - const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ - inputId: InputsModelId.global, - queryId: uniqueQueryId, - }); - const extraVisualizationOptions = useMemo( - () => ({ - breakdownField: stackByField1, - showBuildingBlockAlerts, - showOnlyThreatIndicatorAlerts, - status, - }), - [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status, stackByField1] - ); - useInspectButton({ deleteQuery, loading: isLoadingAlerts, @@ -231,7 +230,6 @@ const AlertsTreemapPanelComponent: React.FC = ({ {isPanelExpanded ? ( isChartEmbeddablesEnabled && getLensAttributes && timerange ? (
; +export const LensEmbeddable = () =>
; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx index 1d38fc7cf8a009..ee4f6810ffac07 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx @@ -18,6 +18,9 @@ import { DEFAULT_STACK_BY_FIELD, DEFAULT_STACK_BY_FIELD1 } from '../common/confi import { TestProviders } from '../../../../common/mock'; import { ChartContextMenu } from '../../../pages/detection_engine/chart_panels/chart_context_menu'; import { TABLE } from '../../../pages/detection_engine/chart_panels/chart_select/translations'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { useInspectButton } from '../common/hooks'; const from = '2022-07-28T08:20:18.966Z'; const to = '2022-07-28T08:20:18.966Z'; @@ -52,21 +55,35 @@ jest.mock('../../../containers/detection_engine/alerts/use_query', () => { }; }); -describe('AlertsCountPanel', () => { - const defaultProps = { - inspectTitle: TABLE, - setStackByField0: jest.fn(), - setStackByField1: jest.fn(), - showBuildingBlockAlerts: false, - showOnlyThreatIndicatorAlerts: false, - signalIndexName: 'signalIndexName', - stackByField0: DEFAULT_STACK_BY_FIELD, - stackByField1: DEFAULT_STACK_BY_FIELD1, - status: 'open' as Status, - }; - const mockSetToggle = jest.fn(); - const mockUseQueryToggle = useQueryToggle as jest.Mock; +jest.mock('../../../../common/hooks/use_experimental_features'); +jest.mock('../../../../common/components/page/use_refetch_by_session'); +jest.mock('../../../../common/components/visualization_actions/lens_embeddable'); +jest.mock('../../../../common/components/page/use_refetch_by_session', () => ({ + useRefetchByRestartingSession: jest.fn().mockReturnValue({ + searchSessionId: 'mockSearchSessionId', + refetchByRestartingSession: jest.fn(), + }), +})); +jest.mock('../common/hooks', () => ({ + useInspectButton: jest.fn(), + useStackByFields: jest.fn(), +})); + +const defaultProps = { + inspectTitle: TABLE, + setStackByField0: jest.fn(), + setStackByField1: jest.fn(), + showBuildingBlockAlerts: false, + showOnlyThreatIndicatorAlerts: false, + signalIndexName: 'signalIndexName', + stackByField0: DEFAULT_STACK_BY_FIELD, + stackByField1: DEFAULT_STACK_BY_FIELD1, + status: 'open' as Status, +}; +const mockUseQueryToggle = useQueryToggle as jest.Mock; +const mockSetToggle = jest.fn(); +describe('AlertsCountPanel', () => { beforeEach(() => { jest.clearAllMocks(); mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); @@ -202,3 +219,59 @@ describe('AlertsCountPanel', () => { }); }); }); + +describe('when isChartEmbeddablesEnabled = true', () => { + const mockSearchSessionId = 'mockSearchSessionId'; + const mockRefetchByRestartingSession = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + + mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); + + (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ + searchSessionId: mockSearchSessionId, + refetchByRestartingSession: mockRefetchByRestartingSession, + }); + + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + }); + + it('refetch data by refetchByRestartingSession', async () => { + await act(async () => { + mount( + + + + ); + expect((useInspectButton as jest.Mock).mock.calls[0][0].refetch).toEqual( + mockRefetchByRestartingSession + ); + expect((useInspectButton as jest.Mock).mock.calls[0][0].searchSessionId).toEqual( + mockSearchSessionId + ); + }); + }); + + it('renders LensEmbeddable', async () => { + await act(async () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="lens-embeddable"]').exists()).toBeTruthy(); + }); + }); + + it('should skip calling getAlertsRiskQuery', async () => { + await act(async () => { + mount( + + + + ); + expect(mockUseQueryAlerts.mock.calls[0][0].skip).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx index d3ead7046d1ace..ccbf87b4e9bfbc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx @@ -21,6 +21,9 @@ import * as helpers from './helpers'; import { mockAlertSearchResponse } from './mock_data'; import { ChartContextMenu } from '../../../pages/detection_engine/chart_panels/chart_context_menu'; import { AlertsHistogramPanel, LEGEND_WITH_COUNTS_WIDTH } from '.'; +import { useInspectButton } from '../common/hooks'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; jest.mock('../../../../common/containers/query_toggle'); @@ -95,6 +98,23 @@ jest.mock('../../../containers/detection_engine/alerts/use_query', () => { useQueryAlerts: (...props: unknown[]) => mockUseQueryAlerts(...props), }; }); +jest.mock('../../../../common/hooks/use_experimental_features'); +jest.mock('../../../../common/components/page/use_refetch_by_session'); +jest.mock('../../../../common/components/visualization_actions/lens_embeddable'); + +jest.mock('../../../../common/components/page/use_refetch_by_session', () => ({ + useRefetchByRestartingSession: jest.fn().mockReturnValue({ + searchSessionId: 'mockSearchSessionId', + refetchByRestartingSession: jest.fn(), + }), +})); +jest.mock('../common/hooks', () => { + const actual = jest.requireActual('../common/hooks'); + return { + ...actual, + useInspectButton: jest.fn(), + }; +}); describe('AlertsHistogramPanel', () => { const defaultProps = { @@ -700,4 +720,60 @@ describe('AlertsHistogramPanel', () => { }); }); }); + + describe('when isChartEmbeddablesEnabled = true', () => { + const mockSearchSessionId = 'mockSearchSessionId'; + const mockRefetchByRestartingSession = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + + mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); + + (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ + searchSessionId: mockSearchSessionId, + refetchByRestartingSession: mockRefetchByRestartingSession, + }); + + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + }); + + it('refetch data by refetchByRestartingSession', async () => { + await act(async () => { + mount( + + + + ); + expect((useInspectButton as jest.Mock).mock.calls[0][0].refetch).toEqual( + mockRefetchByRestartingSession + ); + expect((useInspectButton as jest.Mock).mock.calls[0][0].searchSessionId).toEqual( + mockSearchSessionId + ); + }); + }); + + it('renders LensEmbeddable', async () => { + await act(async () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="lens-embeddable"]').exists()).toBeTruthy(); + }); + }); + + it('should skip calling getAlertsRiskQuery', async () => { + await act(async () => { + mount( + + + + ); + expect(mockUseQueryAlerts.mock.calls[0][0].skip).toBeTruthy(); + }); + }); + }); }); From c31b77d548e120b45db7bc3dbb189f15579c3124 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 28 Dec 2022 11:08:34 +0000 Subject: [PATCH 15/72] fix unit tests --- .../visualization_actions/use_lens_attributes.test.tsx | 2 +- .../pages/detection_engine/chart_panels/index.test.tsx | 9 ++++++++- .../pages/detection_engine/detection_engine.test.tsx | 8 ++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx index ca23d57d2c1955..7a84fc779299c8 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx @@ -30,7 +30,7 @@ describe('useLensAttributes', () => { (useSourcererDataView as jest.Mock).mockReturnValue({ dataViewId: 'security-solution-default', indicesExist: true, - selectedPatterns: ['signal-index'], + selectedPatterns: ['auditbeat-*'], }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx index 1d452ce6819320..f87b1b3fcbbf79 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx @@ -18,9 +18,16 @@ import { TestProviders } from '../../../../common/mock'; import { ChartPanels } from '.'; jest.mock('./alerts_local_storage'); - jest.mock('../../../../common/containers/sourcerer'); +jest.mock('../../../../common/components/visualization_actions/lens_embeddable'); +jest.mock('../../../../common/components/page/use_refetch_by_session', () => ({ + useRefetchByRestartingSession: jest.fn().mockReturnValue({ + searchSessionId: 'mockSearchSessionId', + refetchByRestartingSession: jest.fn(), + }), +})); + jest.mock('react-router-dom', () => { const originalModule = jest.requireActual('react-router-dom'); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index 55430d3e685959..03f600d686a747 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -123,6 +123,14 @@ jest.mock('../../components/alerts_table/timeline_actions/use_bulk_add_to_case_a useBulkAddToCaseActions: jest.fn(() => []), })); +jest.mock('../../../common/components/visualization_actions/lens_embeddable'); +jest.mock('../../../common/components/page/use_refetch_by_session', () => ({ + useRefetchByRestartingSession: jest.fn().mockReturnValue({ + searchSessionId: 'mockSearchSessionId', + refetchByRestartingSession: jest.fn(), + }), +})); + describe('DetectionEnginePageComponent', () => { beforeAll(() => { (useParams as jest.Mock).mockReturnValue({}); From 785712e9efea7668c15d09c04993f30f39e03c15 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 29 Dec 2022 18:26:50 +0000 Subject: [PATCH 16/72] replace alerts donut charts --- .../common/components/charts/donutchart.tsx | 84 ++++++--- .../common/alerts/alerts_donut.ts | 171 ++++++++++++++++++ .../visualization_actions/lens_embeddable.tsx | 43 +++-- .../components/visualization_actions/types.ts | 12 +- .../components/visualization_actions/utils.ts | 10 + .../alert_donut_embeddable.tsx | 75 ++++++++ .../alerts_by_status/alerts_by_status.tsx | 103 +++++++---- .../alerts_by_status/types.ts | 29 +++ 8 files changed, 452 insertions(+), 75 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_donut.ts create mode 100644 x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx index 499ac862c6375f..c8d35282d9d859 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx @@ -5,10 +5,11 @@ * 2.0. */ +import type { EuiFlexGroupProps } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip, useEuiTheme } from '@elastic/eui'; import React, { useMemo } from 'react'; -import type { Datum, NodeColorAccessor, PartialTheme, ElementClickListener } from '@elastic/charts'; +import type { Datum, NodeColorAccessor, PartialTheme } from '@elastic/charts'; import { Chart, Partition, @@ -48,34 +49,40 @@ export interface DonutChartProps { legendItems?: LegendItem[] | null | undefined; title: React.ReactElement | string | number | null; totalCount: number | null | undefined; - onElementClick?: ElementClickListener; +} + +export interface DonutChartWrapperProps { + children?: React.ReactElement; + dataExists: boolean; + label: React.ReactElement | string; + title: React.ReactElement | string | number | null; + isChartEmbeddablesEnabled?: boolean; } /* Make this position absolute in order to overlap the text onto the donut */ -const DonutTextWrapper = styled(EuiFlexGroup)` - top: 34%; +export const DonutTextWrapper = styled(EuiFlexGroup)< + EuiFlexGroupProps & { $isChartEmbeddablesEnabled?: boolean; $dataExists?: boolean } +>` + top: ${({ $isChartEmbeddablesEnabled, $dataExists }) => + $isChartEmbeddablesEnabled && $dataExists ? `34%;` : `66%`}; width: 100%; max-width: 77px; position: absolute; z-index: 1; `; -const StyledEuiFlexItem = styled(EuiFlexItem)` +export const StyledEuiFlexItem = styled(EuiFlexItem)` position: relative; align-items: center; `; -export const DonutChart = ({ - data, - fillColor, - height = 90, +const DonutChartWrapperComponent: React.FC = ({ + dataExists, label, - legendItems, + children, title, - totalCount, - onElementClick, -}: DonutChartProps) => { - const theme = useTheme(); + isChartEmbeddablesEnabled, +}) => { const { euiTheme } = useEuiTheme(); const emptyLabelStyle = useMemo( () => ({ @@ -83,7 +90,6 @@ export const DonutChart = ({ }), [euiTheme.colors.disabled] ); - return ( - + {title} - + {label} + {children} + + + ); +}; +export const DonutChartWrapper = React.memo(DonutChartWrapperComponent); + +export const DonutChart = ({ + data, + height = 90, + fillColor, + label, + legendItems, + title, + totalCount, +}: DonutChartProps) => { + const theme = useTheme(); + + return ( + 0} label={label} title={title}> + <> {data == null || totalCount == null || totalCount === 0 ? ( ) : ( - + )} - - {legendItems && legendItems?.length > 0 && ( - - - - )} - + + {legendItems && legendItems?.length > 0 && ( + + + + )} + + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_donut.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_donut.ts new file mode 100644 index 00000000000000..ea8723f9231343 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_donut.ts @@ -0,0 +1,171 @@ +/* + * 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 type { GetLensAttributes, LensAttributes } from '../../../types'; + +export const getAlertsBySeverityAttributes: GetLensAttributes = ( + stackByField = 'kibana.alert.workflow_status', + extraOptions +) => + ({ + title: 'Alerts', + description: '', + visualizationType: 'lnsPie', + state: { + visualization: { + shape: 'donut', + layers: [ + { + layerId: '51ed355e-6e23-4038-a417-f653a1160370', + primaryGroups: ['a9b43606-7ff7-46ae-a47c-85bed80fab9a'], + metrics: ['21cc4a49-3780-4b1a-be28-f02fa5303d24'], + numberDisplay: 'value', + categoryDisplay: 'hide', + legendDisplay: 'hide', + nestedLegend: true, + layerType: 'data', + emptySizeRatio: 0.85, + percentDecimals: 2, + }, + ], + }, + query: { + query: '', + language: 'kuery', + }, + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + index: 'a1aaa83b-5026-444e-9465-50e0afade01c', + key: stackByField, + field: stackByField, + params: { + query: extraOptions?.status, + }, + type: 'phrase', + }, + query: { + match_phrase: { + [stackByField]: extraOptions?.status, + }, + }, + }, + { + meta: { + type: 'phrases', + key: '_index', + params: ['.alerts-security.alerts-default'], + alias: null, + negate: false, + disabled: false, + }, + query: { + bool: { + should: [ + { + match_phrase: { + _index: '.alerts-security.alerts-default', + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + ], + datasourceStates: { + formBased: { + layers: { + '51ed355e-6e23-4038-a417-f653a1160370': { + columns: { + 'a9b43606-7ff7-46ae-a47c-85bed80fab9a': { + label: 'Filters', + dataType: 'string', + operationType: 'filters', + scale: 'ordinal', + isBucketed: true, + params: { + filters: [ + { + input: { + query: 'kibana.alert.severity: "critical"', + language: 'kuery', + }, + label: 'Critical', + }, + { + label: 'High', + input: { + query: 'kibana.alert.severity : "high" ', + language: 'kuery', + }, + }, + { + input: { + query: 'kibana.alert.severity: "medium"', + language: 'kuery', + }, + label: 'Medium', + }, + { + input: { + query: 'kibana.alert.severity : "low" ', + language: 'kuery', + }, + label: 'Low', + }, + ], + }, + }, + '21cc4a49-3780-4b1a-be28-f02fa5303d24': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + filter: { + query: '', + language: 'kuery', + }, + params: { + emptyAsNull: true, + }, + }, + }, + columnOrder: [ + 'a9b43606-7ff7-46ae-a47c-85bed80fab9a', + '21cc4a49-3780-4b1a-be28-f02fa5303d24', + ], + sampling: 1, + incompleteColumns: {}, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [], + adHocDataViews: {}, + }, + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: 'indexpattern-datasource-layer-51ed355e-6e23-4038-a417-f653a1160370', + }, + { + type: 'index-pattern', + name: 'a1aaa83b-5026-444e-9465-50e0afade01c', + id: '{dataViewId}', + }, + ], + } as LensAttributes); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 1d6452f3c122dc..b54ddf0d96f40c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -21,11 +21,12 @@ import { inputsSelectors } from '../../store'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { ModalInspectQuery } from '../inspect/modal'; import { InputsModelId } from '../../store/inputs/constants'; -import { getRequestsAndResponses } from './utils'; +import { getRequestsAndResponses, parseVisualizationData } from './utils'; import { SourcererScopeName } from '../../store/sourcerer/model'; -const LensComponentWrapper = styled.div<{ height?: string }>` +const LensComponentWrapper = styled.div<{ height?: string; width?: string }>` height: ${({ height }) => height ?? 'auto'}; + width: ${({ width }) => width ?? 'auto'}; > div { background-color: transparent; } @@ -54,6 +55,7 @@ const LensEmbeddableComponent: React.FC = ({ extraActions, getLensAttributes, height: wrapperHeight, + width: wrapperWidth, id, inputsModelId = InputsModelId.global, inspectTitle, @@ -61,6 +63,7 @@ const LensEmbeddableComponent: React.FC = ({ scopeId = SourcererScopeName.default, stackByField, timerange, + onLoad, }) => { const { lens } = useKibana().services; const dispatch = useDispatch(); @@ -124,28 +127,38 @@ const LensEmbeddableComponent: React.FC = ({ return { response, additionalResponses }; }, [visualizationData.responses]); - const onLoad = useCallback((isLoading, adapters) => { - if (!adapters) { - return; - } - const data = getRequestsAndResponses(adapters?.requests?.getRequests()); - setVisualizationData({ - requests: data.requests, - responses: data.responses, - isLoading, - }); - }, []); + const callback = useCallback( + (isLoading, adapters) => { + if (!adapters) { + return; + } + const data = getRequestsAndResponses(adapters?.requests?.getRequests()); + setVisualizationData({ + requests: data.requests, + responses: data.responses, + isLoading, + }); + if (onLoad != null) { + onLoad({ + requests: parseVisualizationData(data.requests), + responses: parseVisualizationData(data.responses), + isLoading, + }); + } + }, + [onLoad] + ); return ( <> {attributes && searchSessionId ? ( - + void; + export interface LensEmbeddableComponentProps { - extraOptions?: ExtraOptions; extraActions?: Action[]; + extraOptions?: ExtraOptions; getLensAttributes?: GetLensAttributes; height?: string; id: string; inputsModelId?: InputsModelId.global | InputsModelId.timeline; inspectTitle?: string; lensAttributes?: LensAttributes; + onLoad?: OnEmbeddableLoaded; scopeId?: SourcererScopeName; stackByField?: string; timerange: { from: string; to: string }; + width?: string; } export enum RequestStatus { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts index 446c17c33afff7..d606bdecc6e521 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts @@ -168,3 +168,13 @@ export const getRequestsAndResponses = (requests: Request[]) => { { requests: [], responses: [] } ); }; + +export const parseVisualizationData = (data: string[]): object[] => { + return data.reduce((acc, req) => { + try { + acc.push(JSON.parse(req)); + // eslint-disable-next-line no-empty + } catch (e) {} + return acc; + }, []); +}; diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx new file mode 100644 index 00000000000000..97662a4af15dca --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx @@ -0,0 +1,75 @@ +/* + * 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, { useCallback, useEffect, useMemo, useState } from 'react'; +import { DonutChartWrapper } from '../../../../common/components/charts/donutchart'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; +import { getAlertsBySeverityAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_donut'; +import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; +import type { EmbeddableData } from '../../../../common/components/visualization_actions/types'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { ChartLabel } from './chart_label'; +import type { AlertDonutEmbeddableProps, VisualizationAlertsByStatusData } from './types'; + +const ChartSize = '135px'; + +const AlertDonutEmbeddableComponent: React.FC = ({ + status, + setQuery, + timerange, + label, +}) => { + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: `alertsStatus${status}`, + }); + const [visualizationData, setVisualizationData] = useState(); + + const onEmbeddableLoad = useCallback( + (result: EmbeddableData) => { + setVisualizationData(result as VisualizationAlertsByStatusData); + }, + [setVisualizationData] + ); + + const extraOptions = useMemo(() => ({ status }), [status]); + + useEffect(() => { + setQuery({ + id: `alertsStatus${status}`, + searchSessionId, + refetch: refetchByRestartingSession, + loading: false, + inspect: null, + }); + }, [refetchByRestartingSession, searchSessionId, setQuery, status]); + + const dataExists = visualizationData != null && visualizationData.responses[0].hits.total !== 0; + + return ( + : null} + > + + + ); +}; + +export const AlertDonutEmbeddable = React.memo(AlertDonutEmbeddableComponent); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx index 751be9e6cbaec4..37be689dde9296 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx @@ -53,6 +53,9 @@ import { emptyDonutColor } from '../../../../common/components/charts/donutchart import { LastUpdatedAt } from '../../../../common/components/last_updated_at'; import { LinkButton, useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; import { useNavigateToTimeline } from '../hooks/use_navigate_to_timeline'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { useGlobalTime } from '../../../../common/containers/use_global_time'; +import { AlertDonutEmbeddable } from './alert_donut_embeddable'; const StyledFlexItem = styled(EuiFlexItem)` padding: 0 4px; @@ -93,6 +96,9 @@ export const AlertsByStatus = ({ const { onClick: goToAlerts, href } = useGetSecuritySolutionLinkProps()({ deepLinkId: SecurityPageName.alerts, }); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const { to, from, setQuery } = useGlobalTime(); + const timerange = useMemo(() => ({ from, to }), [from, to]); const isLargerBreakpoint = useIsWithinMinBreakpoint('xl'); const isSmallBreakpoint = useIsWithinMaxBreakpoint('s'); @@ -117,7 +123,7 @@ export const AlertsByStatus = ({ additionalFilters, entityFilter, signalIndexName, - skip: !toggleStatus, + skip: !toggleStatus || isChartEmbeddablesEnabled, queryId: DETECTION_RESPONSE_ALERTS_BY_STATUS_ID, }); const legendItems: LegendItem[] = useMemo( @@ -161,6 +167,7 @@ export const AlertsByStatus = ({ inspectMultiple toggleStatus={toggleStatus} toggleQuery={setToggleStatus} + showInspectButton={!isChartEmbeddablesEnabled} > @@ -177,7 +184,7 @@ export const AlertsByStatus = ({ {toggleStatus && ( <> - + {totalAlerts !== 0 && ( <> @@ -191,41 +198,73 @@ export const AlertsByStatus = ({ )} - - } - totalCount={openCount} - /> + + {isChartEmbeddablesEnabled ? ( + + ) : ( + } + totalCount={openCount} + /> + )} - - } - totalCount={acknowledgedCount} - /> + + {isChartEmbeddablesEnabled ? ( + + ) : ( + } + totalCount={acknowledgedCount} + /> + )} - - } - totalCount={closedCount} - /> + + {isChartEmbeddablesEnabled ? ( + + ) : ( + } + totalCount={closedCount} + /> + )} - - {legendItems.length > 0 && } - + {!isChartEmbeddablesEnabled && ( + + {legendItems.length > 0 && } + + )} diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts index 31e61ae22e6b2c..252117864181d2 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts @@ -7,6 +7,7 @@ import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import type { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; +import type { GlobalTimeArgs } from '../../../../common/containers/use_global_time'; interface StatusBySeverity { doc_count_error_upper_bound: number; @@ -51,6 +52,21 @@ export interface AlertsByStatusResponse }; } +export interface VisualizationAlertsByStatusResponse { + took: number; + _shards: { + total: number; + successful: number; + skipped: number; + failed: number; + }; + aggregations?: Aggregations; + hits: { + total: number; + hits: Hit[]; + }; +} + export interface SeverityBuckets { key: Severity; value: number; @@ -59,3 +75,16 @@ export interface SeverityBuckets { export type ParsedAlertsData = Partial< Record > | null; + +export interface AlertDonutEmbeddableProps { + status: Status; + setQuery: GlobalTimeArgs['setQuery']; + timerange: { from: string; to: string }; + label: string; +} + +export interface VisualizationAlertsByStatusData { + responses: VisualizationAlertsByStatusResponse[]; + requests: Array<{}>; + isLoading: boolean; +} From c753bd81e020f8b35890a5bf58fd6f0ae5092b45 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 30 Dec 2022 16:02:53 +0000 Subject: [PATCH 17/72] add total alerts count --- .../visualization_actions/lens_embeddable.tsx | 6 +- .../components/visualization_actions/types.ts | 4 +- .../components/visualization_actions/utils.ts | 15 ++-- .../alert_donut_embeddable.tsx | 33 ++++++--- .../alerts_by_status/alerts_by_status.tsx | 72 ++++++++++++++++--- .../alerts_by_status/types.ts | 22 ++++-- 6 files changed, 115 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index b54ddf0d96f40c..613e1fd7118973 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -133,15 +133,17 @@ const LensEmbeddableComponent: React.FC = ({ return; } const data = getRequestsAndResponses(adapters?.requests?.getRequests()); + setVisualizationData({ requests: data.requests, responses: data.responses, isLoading, }); + if (onLoad != null) { onLoad({ - requests: parseVisualizationData(data.requests), - responses: parseVisualizationData(data.responses), + requests: data.requests, + responses: data.responses, isLoading, }); } diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index 7ae778de9f7c28..2cf2afd79d2b66 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -34,8 +34,8 @@ export interface VisualizationActionsProps { } export interface EmbeddableData { - requests: object[]; - responses: object[]; + requests: string[]; + responses: string[]; isLoading: boolean; } diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts index d606bdecc6e521..9fda9492812c58 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts @@ -169,12 +169,11 @@ export const getRequestsAndResponses = (requests: Request[]) => { ); }; -export const parseVisualizationData = (data: string[]): object[] => { - return data.reduce((acc, req) => { +export const parseVisualizationData = (data: string[]): T[] => + data.reduce((acc, curr) => { try { - acc.push(JSON.parse(req)); - // eslint-disable-next-line no-empty - } catch (e) {} - return acc; - }, []); -}; + return [...acc, JSON.parse(curr)]; + } catch (e) { + return acc; + } + }, [] as T[]); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx index 97662a4af15dca..80f72934d73f5d 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx @@ -4,16 +4,20 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { DonutChartWrapper } from '../../../../common/components/charts/donutchart'; import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; import { getAlertsBySeverityAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_donut'; import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; import type { EmbeddableData } from '../../../../common/components/visualization_actions/types'; +import { parseVisualizationData } from '../../../../common/components/visualization_actions/utils'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { inputsSelectors } from '../../../../common/store/inputs'; import { InputsModelId } from '../../../../common/store/inputs/constants'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { ChartLabel } from './chart_label'; -import type { AlertDonutEmbeddableProps, VisualizationAlertsByStatusData } from './types'; +import type { AlertDonutEmbeddableProps, VisualizationAlertsByStatusResponse } from './types'; +import { AlertsByStatusQueryId } from './types'; const ChartSize = '135px'; @@ -23,17 +27,28 @@ const AlertDonutEmbeddableComponent: React.FC = ({ timerange, label, }) => { + const queryId = AlertsByStatusQueryId[`${status}AlertsQuery`]; const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ inputId: InputsModelId.global, - queryId: `alertsStatus${status}`, + queryId, }); - const [visualizationData, setVisualizationData] = useState(); + const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); + const { inspect } = useDeepEqualSelector((state) => getGlobalQuery(state, queryId)); + const visualizationData = inspect?.response + ? parseVisualizationData(inspect?.response) + : null; const onEmbeddableLoad = useCallback( - (result: EmbeddableData) => { - setVisualizationData(result as VisualizationAlertsByStatusData); + ({ requests, responses, isLoading }: EmbeddableData) => { + setQuery({ + id: queryId, + searchSessionId, + refetch: refetchByRestartingSession, + loading: isLoading, + inspect: { dsl: requests, response: responses }, + }); }, - [setVisualizationData] + [queryId, refetchByRestartingSession, searchSessionId, setQuery] ); const extraOptions = useMemo(() => ({ status }), [status]); @@ -48,14 +63,14 @@ const AlertDonutEmbeddableComponent: React.FC = ({ }); }, [refetchByRestartingSession, searchSessionId, setQuery, status]); - const dataExists = visualizationData != null && visualizationData.responses[0].hits.total !== 0; + const dataExists = visualizationData != null && visualizationData[0].hits.total !== 0; return ( : null} + title={dataExists ? : null} > + getGlobalQuery(state, AlertsByStatusQueryId.openAlertsQuery) + ); + const { inspect: inspectAcknowledgedAlerts } = useDeepEqualSelector((state) => + getGlobalQuery(state, AlertsByStatusQueryId.acknowledgedAlertsQuery) + ); + const { inspect: inspectClosedAlerts } = useDeepEqualSelector((state) => + getGlobalQuery(state, AlertsByStatusQueryId.closedAlertsQuery) + ); + const visualizationOpenAlertsData = + inspectOpenAlerts != null + ? parseVisualizationData(inspectOpenAlerts?.response)[0] + .hits.total + : 0; + const visualizationAcknowledgedAlertsData = + inspectAcknowledgedAlerts != null + ? parseVisualizationData( + inspectAcknowledgedAlerts?.response + )[0].hits.total + : 0; + const visualizationClosedAlertsData = + inspectClosedAlerts != null + ? parseVisualizationData( + inspectClosedAlerts?.response + )[0].hits.total + : 0; + + const visualizationTotalAlertsData = + visualizationOpenAlertsData + + visualizationAcknowledgedAlertsData + + visualizationClosedAlertsData ?? 0; + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const { to, from, setQuery } = useGlobalTime(); const timerange = useMemo(() => ({ from, to }), [from, to]); @@ -185,17 +224,28 @@ export const AlertsByStatus = ({ <> - {totalAlerts !== 0 && ( - - <> - - - - <> - {ALERTS(totalAlerts)} - - - )} + {totalAlerts !== 0 || + (visualizationTotalAlertsData !== 0 && ( + + <> + + + + <> + + {ALERTS( + isChartEmbeddablesEnabled ? visualizationTotalAlertsData : totalAlerts + )} + + + + ))} diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts index 252117864181d2..fe159943280fca 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts @@ -6,7 +6,7 @@ */ import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; +import type { ESQuery } from '../../../../../common/typed_json'; import type { GlobalTimeArgs } from '../../../../common/containers/use_global_time'; interface StatusBySeverity { @@ -16,7 +16,7 @@ interface StatusBySeverity { } interface StatusBucket { - key: Status; + key: AlertsByStatus; doc_count: number; statusBySeverity?: StatusBySeverity; } @@ -73,11 +73,13 @@ export interface SeverityBuckets { label?: string; } export type ParsedAlertsData = Partial< - Record + Record > | null; +export type AlertsByStatus = 'open' | 'acknowledged' | 'closed'; + export interface AlertDonutEmbeddableProps { - status: Status; + status: AlertsByStatus; setQuery: GlobalTimeArgs['setQuery']; timerange: { from: string; to: string }; label: string; @@ -85,6 +87,16 @@ export interface AlertDonutEmbeddableProps { export interface VisualizationAlertsByStatusData { responses: VisualizationAlertsByStatusResponse[]; - requests: Array<{}>; + requests: ESQuery[]; isLoading: boolean; } +export interface VisualizationInspectQuery { + dsl: ESQuery[]; + response: VisualizationAlertsByStatusResponse[]; +} + +export enum AlertsByStatusQueryId { + 'openAlertsQuery' = 'openAlertsQuery', + 'acknowledgedAlertsQuery' = 'acknowledgedAlertsQuery', + 'closedAlertsQuery' = 'closedAlertsQuery', +} From dfb9e8fd58b0f5492c4d9e14647a66782812fd7e Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 3 Jan 2023 15:48:36 +0000 Subject: [PATCH 18/72] render rule preview with lens --- .../common/components/inspect/modal.tsx | 11 +- .../common/alerts/rule_preview.ts | 162 ++++++++++++++++++ .../visualization_actions/lens_embeddable.tsx | 45 +++-- .../components/visualization_actions/types.ts | 5 +- .../use_lens_attributes.tsx | 30 ++-- .../components/visualization_actions/utils.ts | 4 +- .../alerts_histogram_panel/index.tsx | 15 ++ .../rules/rule_preview/preview_histogram.tsx | 54 +++++- .../rule_preview/use_preview_histogram.tsx | 6 +- 9 files changed, 301 insertions(+), 31 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx index e46c2bca723f24..b9e27c921704ab 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx @@ -45,6 +45,7 @@ const DescriptionListStyled = styled(EuiDescriptionList)` DescriptionListStyled.displayName = 'DescriptionListStyled'; export interface ModalInspectProps { + adHocDataViews?: string[] | null; additionalRequests?: string[] | null; additionalResponses?: string[] | null; closeModal: () => void; @@ -108,6 +109,7 @@ export const formatIndexPatternRequested = (indices: string[] = []) => { }; export const ModalInspectQuery = ({ + adHocDataViews, additionalRequests, additionalResponses, closeModal, @@ -120,6 +122,7 @@ export const ModalInspectQuery = ({ const { selectedPatterns } = useSourcererDataView( inputId === 'timeline' ? SourcererScopeName.timeline : getScopeFromPath(pathname) ); + const requests: string[] = [request, ...(additionalRequests != null ? additionalRequests : [])]; const responses: string[] = [ response, @@ -150,7 +153,13 @@ export const ModalInspectQuery = ({ ), description: ( -

{formatIndexPatternRequested(inspectRequests[0]?.index ?? [])}

+

+ {formatIndexPatternRequested( + adHocDataViews != null && adHocDataViews.length > 0 + ? adHocDataViews + : inspectRequests[0]?.index ?? [] + )} +

{!isSourcererPattern && (

diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts new file mode 100644 index 00000000000000..fc7071cc8afffd --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts @@ -0,0 +1,162 @@ +/* + * 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 type { GetLensAttributes } from '../../../types'; + +export const getRulePreviewLensAttributes: GetLensAttributes = ( + stackByField = 'event.category', + extraOptions +) => ({ + title: 'alert preview', + description: '', + visualizationType: 'lnsXY', + state: { + visualization: { + title: 'Empty XY chart', + legend: { + isVisible: false, + position: 'left', + }, + valueLabels: 'hide', + preferredSeriesType: 'bar_stacked', + layers: [ + { + layerId: '1469e8f5-c685-4f37-96f4-2084c0537bf7', + accessors: ['9c89324b-0c59-4403-9698-d989a09dc5a8'], + position: 'top', + seriesType: 'bar_stacked', + showGridlines: false, + layerType: 'data', + xAccessor: 'eba07b4d-766d-49d7-8435-d40367d3d055', + splitAccessor: 'e92c8920-0449-4564-81f4-8945517817a4', + }, + ], + valuesInLegend: true, + yTitle: '', + axisTitlesVisibilitySettings: { + x: false, + yLeft: false, + yRight: true, + }, + }, + query: { + query: '', + language: 'kuery', + }, + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + index: '1f76cd3c-7267-424d-9bbc-213c8c3def61', + key: 'kibana.alert.rule.uuid', + field: 'kibana.alert.rule.uuid', + params: { + query: extraOptions?.ruleId, + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'kibana.alert.rule.uuid': extraOptions?.ruleId, + }, + }, + }, + ], + datasourceStates: { + formBased: { + layers: { + '1469e8f5-c685-4f37-96f4-2084c0537bf7': { + columns: { + '9c89324b-0c59-4403-9698-d989a09dc5a8': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + params: { + emptyAsNull: true, + }, + }, + 'eba07b4d-766d-49d7-8435-d40367d3d055': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { + interval: 'auto', + includeEmptyRows: true, + dropPartials: false, + }, + }, + 'e92c8920-0449-4564-81f4-8945517817a4': { + label: `Top 10 values of ${stackByField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: stackByField, + isBucketed: true, + params: { + size: 10, + orderBy: { + type: 'column', + columnId: '9c89324b-0c59-4403-9698-d989a09dc5a8', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, + }, + columnOrder: [ + 'e92c8920-0449-4564-81f4-8945517817a4', + 'eba07b4d-766d-49d7-8435-d40367d3d055', + '9c89324b-0c59-4403-9698-d989a09dc5a8', + ], + sampling: 1, + incompleteColumns: {}, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [ + { + type: 'index-pattern', + id: '1f76cd3c-7267-424d-9bbc-213c8c3def61', + name: 'indexpattern-datasource-layer-1469e8f5-c685-4f37-96f4-2084c0537bf7', + }, + ], + adHocDataViews: { + '1f76cd3c-7267-424d-9bbc-213c8c3def61': { + id: '1f76cd3c-7267-424d-9bbc-213c8c3def61', + title: `.preview.alerts-security.alerts-${extraOptions?.spaceId}`, + timeFieldName: '@timestamp', + sourceFilters: [], + fieldFormats: {}, + runtimeFieldMap: {}, + fieldAttrs: {}, + allowNoIndex: false, + name: `.preview.alerts-security.alerts-${extraOptions?.spaceId}`, + }, + }, + }, + references: [], +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 613e1fd7118973..0725b55a03c8db 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -21,7 +21,7 @@ import { inputsSelectors } from '../../store'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { ModalInspectQuery } from '../inspect/modal'; import { InputsModelId } from '../../store/inputs/constants'; -import { getRequestsAndResponses, parseVisualizationData } from './utils'; +import { getRequestsAndResponses } from './utils'; import { SourcererScopeName } from '../../store/sourcerer/model'; const LensComponentWrapper = styled.div<{ height?: string; width?: string }>` @@ -151,9 +151,38 @@ const LensEmbeddableComponent: React.FC = ({ [onLoad] ); + const adHocDataViews = useMemo( + () => + attributes?.state?.adHocDataViews != null + ? Object.values(attributes?.state?.adHocDataViews).reduce((acc, adHocDataView) => { + if (adHocDataView?.name != null) { + acc.push(adHocDataView?.name); + } + return acc; + }, [] as string[]) + : null, + [attributes?.state?.adHocDataViews] + ); + + if ( + !attributes || + (visualizationData?.responses != null && visualizationData?.responses?.length === 0) + ) { + return ( + + } + /> + ); + } + return ( <> - {attributes && searchSessionId ? ( + {attributes && searchSessionId && ( = ({ showInspector={false} /> - ) : ( - - } - /> )} - {isShowingModal && requests.request !== null && responses.response !== null && ( + {isShowingModal && requests.request != null && responses.response != null && ( void; export interface LensEmbeddableComponentProps { + adHocDataViews?: string[]; extraActions?: Action[]; extraOptions?: ExtraOptions; getLensAttributes?: GetLensAttributes; @@ -96,8 +97,10 @@ export interface Response { } export interface ExtraOptions { + breakdownField?: string; + ruleId?: string; showBuildingBlockAlerts?: boolean; showOnlyThreatIndicatorAlerts?: boolean; + spaceId?: string; status?: Status; - breakdownField?: string; } diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index 17ece9461edbd7..157f0b6d14929b 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -66,17 +66,23 @@ export const useLensAttributes = ({ return []; }, [detailName, pageName]); - const indexFilters = useMemo(() => getIndexFilters(selectedPatterns), [selectedPatterns]); + const attrs: LensAttributes = useMemo( + () => + lensAttributes ?? + ((getLensAttributes && + stackByField && + getLensAttributes(stackByField, extraOptions)) as LensAttributes), + [extraOptions, getLensAttributes, lensAttributes, stackByField] + ); + + const hasAdHocDataViews = Object.values(attrs?.state?.adHocDataViews ?? {}).length > 0; const lensAttrsWithInjectedData = useMemo(() => { if (lensAttributes == null && (getLensAttributes == null || stackByField == null)) { return null; } - const attrs: LensAttributes = - lensAttributes ?? - ((getLensAttributes && - stackByField && - getLensAttributes(stackByField, extraOptions)) as LensAttributes); + + const indexFilters = hasAdHocDataViews ? [] : getIndexFilters(selectedPatterns); return { ...attrs, @@ -92,24 +98,26 @@ export const useLensAttributes = ({ ...indexFilters, ], }, - references: attrs.references.map((ref: { id: string; name: string; type: string }) => ({ + references: attrs?.references?.map((ref: { id: string; name: string; type: string }) => ({ ...ref, id: dataViewId, })), } as LensAttributes; }, [ + attrs, dataViewId, - extraOptions, filters, getLensAttributes, - indexFilters, + hasAdHocDataViews, lensAttributes, pageFilters, query, + selectedPatterns, stackByField, tabsFilters, title, ]); - - return indicesExist ? lensAttrsWithInjectedData : null; + return hasAdHocDataViews || (!hasAdHocDataViews && indicesExist) + ? lensAttrsWithInjectedData + : null; }; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts index 9fda9492812c58..7c4b087ab4ba57 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts @@ -147,8 +147,8 @@ export const getIndexFilters = (selectedPatterns: string[]) => ] : []; -export const getRequestsAndResponses = (requests: Request[]) => { - return requests.reduce( +export const getRequestsAndResponses = (requests: Request[] | null | undefined) => { + return (requests ?? []).reduce( (acc: { requests: string[]; responses: string[] }, req: Request) => { return { requests: [ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index aa04fef84ea1da..ac1aa5d00423b4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -58,6 +58,7 @@ import { InputsModelId } from '../../../../common/store/inputs/constants'; import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import type { Status } from '../../../../../common/detection_engine/schemas/common'; +import type { EmbeddableData } from '../../../../common/components/visualization_actions/types'; const defaultTotalAlertsObj: AlertsTotal = { value: 0, @@ -302,6 +303,19 @@ export const AlertsHistogramPanel = memo( uniqueQueryId, }); + const onEmbeddableLoad = useCallback( + ({ requests, responses, isLoading }: EmbeddableData) => { + setQuery({ + id: uniqueQueryId, + searchSessionId, + refetch: refetchByRestartingSession, + loading: isLoading, + inspect: { dsl: requests, response: responses }, + }); + }, + [refetchByRestartingSession, searchSessionId, setQuery, uniqueQueryId] + ); + useEffect(() => { setTotalAlertsObj( alertsData?.hits.total ?? { @@ -448,6 +462,7 @@ export const AlertsHistogramPanel = memo( scopeId={SourcererScopeName.detections} stackByField={selectedStackByOption} timerange={timerange} + onLoad={onEmbeddableLoad} /> ) : isInitialLoading ? ( diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx index d4b2fee185cef2..54c35609753c68 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx @@ -38,6 +38,11 @@ import { useGlobalFullScreen } from '../../../../common/containers/use_full_scre import type { TimeframePreviewOptions } from '../../../pages/detection_engine/rules/types'; import { useLicense } from '../../../../common/hooks/use_license'; import { useKibana } from '../../../../common/lib/kibana'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; +import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; +import { getRulePreviewLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/rule_preview'; const LoadingChart = styled(EuiLoadingChart)` display: block; @@ -53,6 +58,8 @@ const FullScreenContainer = styled.div<{ $isFullScreen: boolean }>` export const ID = 'previewHistogram'; +export const CHART_HEIGHT = 150; + interface PreviewHistogramProps { previewId: string; addNoiseWarning: () => void; @@ -89,6 +96,20 @@ export const PreviewHistogram = ({ const isEqlRule = useMemo(() => ruleType === 'eql', [ruleType]); const isMlRule = useMemo(() => ruleType === 'machine_learning', [ruleType]); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const timerange = useMemo(() => ({ from: startDate, to: endDate }), [startDate, endDate]); + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: `${ID}-${previewId}`, + }); + const extraVisualizationOptions = useMemo( + () => ({ + ruleId: previewId, + spaceId, + }), + [previewId, spaceId] + ); + const [isLoading, { data, inspect, totalCount, refetch }] = usePreviewHistogram({ previewId, startDate, @@ -96,6 +117,7 @@ export const PreviewHistogram = ({ spaceId, indexPattern, ruleType, + skip: isChartEmbeddablesEnabled, }); const license = useLicense(); const { browserFields, runtimeMappings } = useSourcererDataView(SourcererScopeName.detections); @@ -113,9 +135,25 @@ export const PreviewHistogram = ({ useEffect((): void => { if (!isLoading && !isInitializing) { - setQuery({ id: `${ID}-${previewId}`, inspect, loading: isLoading, refetch }); + setQuery({ + id: `${ID}-${previewId}`, + inspect, + loading: isLoading, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + searchSessionId, + }); } - }, [setQuery, inspect, isLoading, isInitializing, refetch, previewId]); + }, [ + setQuery, + inspect, + isLoading, + isInitializing, + refetch, + previewId, + isChartEmbeddablesEnabled, + refetchByRestartingSession, + searchSessionId, + ]); const barConfig = useMemo( (): ChartSeriesConfigs => getHistogramConfig(endDate, startDate, !isEqlRule), @@ -161,11 +199,23 @@ export const PreviewHistogram = ({ id={`${ID}-${previewId}`} title={i18n.QUERY_GRAPH_HITS_TITLE} titleSize="xs" + showInspectButton={!isChartEmbeddablesEnabled} /> {isLoading ? ( + ) : isChartEmbeddablesEnabled ? ( + ) : ( { const { uiSettings } = useKibana().services; @@ -55,9 +57,9 @@ export const usePreviewHistogram = ({ stackByField, startDate, includeMissingData: false, - skip: error != null, + skip: skip || error != null, }; - }, [startDate, endDate, filterQuery, spaceId, error, stackByField]); + }, [endDate, filterQuery, spaceId, stackByField, startDate, skip, error]); return useMatrixHistogramCombined(matrixHistogramRequest); }; From 73e17de47526efe1a58331fb159784b0d6ec72ed Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 3 Jan 2023 17:38:29 +0000 Subject: [PATCH 19/72] pass extra filters to Lens --- .../components/alerts_treemap_panel/index.tsx | 13 +--- .../common/components/charts/donutchart.tsx | 8 +- .../common/alerts/alerts_histogram.ts | 8 +- .../common/alerts/alerts_table.ts | 16 ++-- .../common/alerts/alerts_treemap.ts | 12 +-- .../lens_attributes/common/alerts/utils.ts | 76 ------------------- .../visualization_actions/lens_embeddable.tsx | 6 +- .../components/visualization_actions/types.ts | 6 +- .../use_lens_attributes.tsx | 1 - .../alerts_kpis/alerts_count_panel/index.tsx | 12 +-- .../alerts_histogram_panel/index.tsx | 13 +--- .../detection_engine/chart_panels/index.tsx | 16 ---- .../detection_engine/detection_engine.tsx | 3 - 13 files changed, 29 insertions(+), 161 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index 096744fd5f5452..9f558951611f1c 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -31,7 +31,6 @@ import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; import { LensEmbeddable } from '../visualization_actions/lens_embeddable'; import { getAlertsTreemapLensAttributes as getLensAttributes } from '../visualization_actions/lens_attributes/common/alerts/alerts_treemap'; import { SourcererScopeName } from '../../store/sourcerer/model'; -import type { Status } from '../../../../common/detection_engine/schemas/common'; const DEFAULT_HEIGHT = DEFAULT_MIN_CHART_HEIGHT + 134; // px @@ -56,15 +55,12 @@ export interface Props { setStackByField0ComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; setStackByField1: (stackBy: string | undefined) => void; setStackByField1ComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; - showBuildingBlockAlerts: boolean; - showOnlyThreatIndicatorAlerts: boolean; signalIndexName: string | null; stackByField0: string; stackByField0ComboboxRef?: React.RefObject>; stackByField1: string | undefined; stackByField1ComboboxRef?: React.RefObject>; stackByWidth?: number; - status: Status; title: React.ReactNode; } @@ -85,15 +81,12 @@ const AlertsTreemapPanelComponent: React.FC = ({ setStackByField0ComboboxInputRef, setStackByField1, setStackByField1ComboboxInputRef, - showBuildingBlockAlerts, - showOnlyThreatIndicatorAlerts, signalIndexName, stackByField0, stackByField0ComboboxRef, stackByField1, stackByField1ComboboxRef, stackByWidth, - status, title, }: Props) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -109,11 +102,9 @@ const AlertsTreemapPanelComponent: React.FC = ({ const extraVisualizationOptions = useMemo( () => ({ breakdownField: stackByField1, - showBuildingBlockAlerts, - showOnlyThreatIndicatorAlerts, - status, + filters, }), - [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status, stackByField1] + [stackByField1, filters] ); const additionalFilters = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx index c8d35282d9d859..437544ae393256 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx @@ -9,7 +9,7 @@ import type { EuiFlexGroupProps } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip, useEuiTheme } from '@elastic/eui'; import React, { useMemo } from 'react'; -import type { Datum, NodeColorAccessor, PartialTheme } from '@elastic/charts'; +import type { Datum, NodeColorAccessor, PartialTheme, ElementClickListener } from '@elastic/charts'; import { Chart, Partition, @@ -47,6 +47,7 @@ export interface DonutChartProps { height?: number; label: React.ReactElement | string; legendItems?: LegendItem[] | null | undefined; + onElementClick?: ElementClickListener; title: React.ReactElement | string | number | null; totalCount: number | null | undefined; } @@ -129,10 +130,11 @@ export const DonutChartWrapper = React.memo(DonutChartWrapperComponent); export const DonutChart = ({ data, - height = 90, fillColor, + height = 90, label, legendItems, + onElementClick, title, totalCount, }: DonutChartProps) => { @@ -145,7 +147,7 @@ export const DonutChart = ({ ) : ( - + ({ title: 'Alerts', @@ -58,7 +54,7 @@ export const getAlertsHistogramLensAttributes: GetLensAttributes = ( query: '', language: 'kuery', }, - filters: buildAlertsOptionsFilters(extraOptions), + filters: extraOptions?.filters ? extraOptions.filters : [], datasourceStates: { formBased: { layers: { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts index 1fddb15ec60e8c..cea14505f2ebcb 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts @@ -5,14 +5,10 @@ * 2.0. */ import type { GetLensAttributes } from '../../../types'; -import { buildAlertsOptionsFilters } from './utils'; export const getAlertsTableLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', - extraOptions = { - showOnlyThreatIndicatorAlerts: false, - showBuildingBlockAlerts: false, - } + extraOptions ) => ({ title: 'Alerts', description: '', @@ -45,7 +41,7 @@ export const getAlertsTableLensAttributes: GetLensAttributes = ( query: '', language: 'kuery', }, - filters: buildAlertsOptionsFilters(extraOptions), + filters: extraOptions?.filters ? extraOptions.filters : [], datasourceStates: { formBased: { layers: { @@ -77,22 +73,22 @@ export const getAlertsTableLensAttributes: GetLensAttributes = ( }, }, 'f04a71a3-399f-4d32-9efc-8a005e989991': { - label: `Count of ${extraOptions.breakdownField}`, + label: `Count of ${extraOptions?.breakdownField}`, dataType: 'number', operationType: 'count', isBucketed: false, scale: 'ratio', - sourceField: extraOptions.breakdownField, + sourceField: extraOptions?.breakdownField, params: { emptyAsNull: true, }, }, '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { - label: `Top values of ${extraOptions.breakdownField}`, + label: `Top values of ${extraOptions?.breakdownField}`, dataType: 'string', operationType: 'terms', scale: 'ordinal', - sourceField: extraOptions.breakdownField, + sourceField: extraOptions?.breakdownField, isBucketed: true, params: { size: 1000, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts index 8b6fcb315f578a..719574b6aa724b 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts @@ -5,14 +5,10 @@ * 2.0. */ import type { GetLensAttributes } from '../../../types'; -import { buildAlertsOptionsFilters } from './utils'; export const getAlertsTreemapLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', - extraOptions = { - showOnlyThreatIndicatorAlerts: false, - showBuildingBlockAlerts: false, - } + extraOptions ) => ({ title: 'Alerts', description: '', @@ -42,7 +38,7 @@ export const getAlertsTreemapLensAttributes: GetLensAttributes = ( query: '', language: 'kuery', }, - filters: buildAlertsOptionsFilters(extraOptions), + filters: extraOptions?.filters ? extraOptions.filters : [], datasourceStates: { formBased: { layers: { @@ -74,11 +70,11 @@ export const getAlertsTreemapLensAttributes: GetLensAttributes = ( }, }, '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { - label: `Top values of ${extraOptions.breakdownField}`, + label: `Top values of ${extraOptions?.breakdownField}`, dataType: 'string', operationType: 'terms', scale: 'ordinal', - sourceField: extraOptions.breakdownField, + sourceField: extraOptions?.breakdownField, isBucketed: true, params: { size: 1000, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts deleted file mode 100644 index 99a1b2363bb1f5..00000000000000 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/utils.ts +++ /dev/null @@ -1,76 +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 type { ExtraOptions } from '../../../types'; - -export const buildAlertsOptionsFilters = ({ - showBuildingBlockAlerts = false, - showOnlyThreatIndicatorAlerts = false, - status, -}: ExtraOptions) => [ - ...(status - ? [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'kibana.alert.workflow_status', - params: { - query: status, - }, - }, - query: { - match_phrase: { - 'kibana.alert.workflow_status': status, - }, - }, - }, - ] - : []), - ...(showOnlyThreatIndicatorAlerts - ? [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'kibana.alert.rule.type', - params: { - query: 'threat_match', - }, - }, - query: { - match_phrase: { - 'kibana.alert.rule.type': 'threat_match', - }, - }, - }, - ] - : []), - ...(!showBuildingBlockAlerts - ? [ - { - meta: { - index: '.alerts-security.alerts-id', - alias: null, - negate: true, - disabled: false, - type: 'exists', - key: 'kibana.alert.building_block_type', - }, - query: { - exists: { - field: 'kibana.alert.building_block_type', - }, - }, - }, - ] - : []), -]; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 0725b55a03c8db..e37d8807ea4ef2 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -51,19 +51,19 @@ const initVisualizationData: { const style = { height: '100%', minWidth: '100px' }; const LensEmbeddableComponent: React.FC = ({ - extraOptions, extraActions, + extraOptions, getLensAttributes, height: wrapperHeight, - width: wrapperWidth, id, inputsModelId = InputsModelId.global, inspectTitle, lensAttributes, + onLoad, scopeId = SourcererScopeName.default, stackByField, timerange, - onLoad, + width: wrapperWidth, }) => { const { lens } = useKibana().services; const dispatch = useDispatch(); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index 78738bdac92494..8b0a84f0843961 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -7,10 +7,11 @@ import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import type { Action } from '@kbn/ui-actions-plugin/public'; +import type { Filter } from '@kbn/es-query'; -import type { Status } from '../../../../common/detection_engine/schemas/common'; import type { InputsModelId } from '../../store/inputs/constants'; import type { SourcererScopeName } from '../../store/sourcerer/model'; +import type { Status } from '../../../../common/detection_engine/schemas/common'; export type LensAttributes = TypedLensByValueInput['attributes']; export type GetLensAttributes = ( @@ -98,9 +99,8 @@ export interface Response { export interface ExtraOptions { breakdownField?: string; + filters?: Filter[]; ruleId?: string; - showBuildingBlockAlerts?: boolean; - showOnlyThreatIndicatorAlerts?: boolean; spaceId?: string; status?: Status; } diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index 157f0b6d14929b..b50e2f05b0645a 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -83,7 +83,6 @@ export const useLensAttributes = ({ } const indexFilters = hasAdHocDataViews ? [] : getIndexFilters(selectedPatterns); - return { ...attrs, ...(title != null ? { title } : {}), diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index 2fa54aeccf50cd..ff4b4b163eeb84 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -51,15 +51,12 @@ interface AlertsCountPanelProps { setStackByField0ComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; setStackByField1: (stackBy: string | undefined) => void; setStackByField1ComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; - showBuildingBlockAlerts: boolean; - showOnlyThreatIndicatorAlerts: boolean; signalIndexName: string | null; stackByField0: string; stackByField0ComboboxRef?: React.RefObject>; stackByField1: string | undefined; stackByField1ComboboxRef?: React.RefObject>; stackByWidth?: number; - status: Status; title?: React.ReactNode; } const ChartHeight = '180px'; @@ -78,15 +75,12 @@ export const AlertsCountPanel = memo( setStackByField0ComboboxInputRef, setStackByField1, setStackByField1ComboboxInputRef, - showBuildingBlockAlerts, - showOnlyThreatIndicatorAlerts, signalIndexName, stackByField0, stackByField0ComboboxRef, stackByField1, stackByField1ComboboxRef, stackByWidth, - status, title = i18n.COUNT_TABLE_TITLE, }) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -134,11 +128,9 @@ export const AlertsCountPanel = memo( const extraVisualizationOptions = useMemo( () => ({ breakdownField: stackByField1, - showBuildingBlockAlerts, - showOnlyThreatIndicatorAlerts, - status, + filters, }), - [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, stackByField1, status] + [filters, stackByField1] ); const { loading: isLoadingAlerts, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index ac1aa5d00423b4..4b6ecd86383076 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -57,7 +57,6 @@ import { getAlertsHistogramLensAttributes as getLensAttributes } from '../../../ import { InputsModelId } from '../../../../common/store/inputs/constants'; import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import type { Status } from '../../../../../common/detection_engine/schemas/common'; import type { EmbeddableData } from '../../../../common/components/visualization_actions/types'; const defaultTotalAlertsObj: AlertsTotal = { @@ -99,18 +98,15 @@ interface AlertsHistogramPanelProps { query?: Query; runtimeMappings?: MappingRuntimeFields; setComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; - showBuildingBlockAlerts: boolean; showCountsInLegend?: boolean; showGroupByPlaceholder?: boolean; showLegend?: boolean; showLinkToAlerts?: boolean; - showOnlyThreatIndicatorAlerts: boolean; showStackBy?: boolean; showTotalAlertsCount?: boolean; signalIndexName: string | null; stackByLabel?: string; stackByWidth?: number; - status?: Status; timelineId?: string; title?: React.ReactNode; titleSize?: EuiTitleSize; @@ -139,18 +135,15 @@ export const AlertsHistogramPanel = memo( query, runtimeMappings, setComboboxInputRef, - showBuildingBlockAlerts, showCountsInLegend = false, showGroupByPlaceholder = false, showLegend = true, showLinkToAlerts = false, - showOnlyThreatIndicatorAlerts, showStackBy = true, showTotalAlertsCount = false, signalIndexName, stackByLabel, stackByWidth, - status, timelineId, title = i18n.HISTOGRAM_HEADER, titleSize = 'm', @@ -203,11 +196,9 @@ export const AlertsHistogramPanel = memo( }); const extraVisualizationOptions = useMemo( () => ({ - showBuildingBlockAlerts, - showOnlyThreatIndicatorAlerts, - status, + filters, }), - [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts, status] + [filters] ); const { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx index 5e6e76195bc5a2..ee6ba3d0efd356 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx @@ -29,7 +29,6 @@ import { } from '../../../components/alerts_kpis/common/config'; import { AlertsCountPanel } from '../../../components/alerts_kpis/alerts_count_panel'; import { GROUP_BY_LABEL } from '../../../components/alerts_kpis/common/translations'; -import type { Status } from '../../../../../common/detection_engine/schemas/common'; import { RESET_GROUP_BY_FIELDS } from '../../../../common/components/chart_settings_popover/configurations/default/translations'; const TABLE_PANEL_HEIGHT = 330; // px @@ -53,9 +52,6 @@ export interface Props { runtimeMappings: MappingRuntimeFields; signalIndexName: string | null; updateDateRangeCallback: UpdateDateRange; - showBuildingBlockAlerts: boolean; - showOnlyThreatIndicatorAlerts: boolean; - filterGroup: Status; } const ChartPanelsComponent: React.FC = ({ @@ -66,9 +62,6 @@ const ChartPanelsComponent: React.FC = ({ runtimeMappings, signalIndexName, updateDateRangeCallback, - showBuildingBlockAlerts, - filterGroup, - showOnlyThreatIndicatorAlerts, }: Props) => { const { alertViewSelection, @@ -198,14 +191,11 @@ const ChartPanelsComponent: React.FC = ({ query={query} runtimeMappings={runtimeMappings} setComboboxInputRef={setStackByField0ComboboxInputRef} - showBuildingBlockAlerts={showBuildingBlockAlerts} showCountsInLegend={true} showGroupByPlaceholder={false} - showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} showTotalAlertsCount={false} signalIndexName={signalIndexName} stackByLabel={GROUP_BY_LABEL} - status={filterGroup} title={title} titleSize={'s'} updateDateRange={updateDateRangeCallback} @@ -232,14 +222,11 @@ const ChartPanelsComponent: React.FC = ({ setStackByField0ComboboxInputRef={setStackByField0ComboboxInputRef} setStackByField1={updateCommonStackBy1} setStackByField1ComboboxInputRef={setStackByField1ComboboxInputRef} - showBuildingBlockAlerts={showBuildingBlockAlerts} - showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} signalIndexName={signalIndexName} stackByField0={countTableStackBy0} stackByField0ComboboxRef={stackByField0ComboboxRef} stackByField1={countTableStackBy1} stackByField1ComboboxRef={stackByField1ComboboxRef} - status={filterGroup} title={title} /> )} @@ -267,14 +254,11 @@ const ChartPanelsComponent: React.FC = ({ setStackByField0ComboboxInputRef={setStackByField0ComboboxInputRef} setStackByField1={updateCommonStackBy1} setStackByField1ComboboxInputRef={setStackByField1ComboboxInputRef} - showBuildingBlockAlerts={showBuildingBlockAlerts} - showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} signalIndexName={signalIndexName} stackByField0={riskChartStackBy0} stackByField0ComboboxRef={stackByField0ComboboxRef} stackByField1={riskChartStackBy1} stackByField1ComboboxRef={stackByField1ComboboxRef} - status={filterGroup} title={title} /> )} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index 2f63066c62d5a1..8109cd00cdbcc3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -324,12 +324,9 @@ const DetectionEnginePageComponent: React.FC = () From 1c5febb82df3393426093ab25f8a42d854e0c00e Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 3 Jan 2023 17:43:29 +0000 Subject: [PATCH 20/72] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../components/alerts_kpis/alerts_count_panel/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index ff4b4b163eeb84..ffdf68642bd24e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -33,7 +33,6 @@ import { InputsModelId } from '../../../../common/store/inputs/constants'; import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; import { getAlertsTableLensAttributes as getLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_table'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import type { Status } from '../../../../../common/detection_engine/schemas/common'; import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; export const DETECTIONS_ALERTS_COUNT_ID = 'detections-alerts-count'; From 1c18042c93b9e28beff317a02a681b4e41099de7 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 5 Jan 2023 12:47:56 +0000 Subject: [PATCH 21/72] styling --- .../common/components/charts/donutchart.tsx | 23 +++++++++++-------- .../alerts_by_status/alerts_by_status.tsx | 3 +++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx index 437544ae393256..c19887636a44b8 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx @@ -45,6 +45,7 @@ export interface DonutChartProps { data: DonutChartData[] | null | undefined; fillColor: FillColor; height?: number; + isChartEmbeddablesEnabled?: boolean; label: React.ReactElement | string; legendItems?: LegendItem[] | null | undefined; onElementClick?: ElementClickListener; @@ -62,10 +63,9 @@ export interface DonutChartWrapperProps { /* Make this position absolute in order to overlap the text onto the donut */ export const DonutTextWrapper = styled(EuiFlexGroup)< - EuiFlexGroupProps & { $isChartEmbeddablesEnabled?: boolean; $dataExists?: boolean } + EuiFlexGroupProps & { $isChartEmbeddablesEnabled?: boolean } >` - top: ${({ $isChartEmbeddablesEnabled, $dataExists }) => - $isChartEmbeddablesEnabled && $dataExists ? `34%;` : `66%`}; + top: ${({ $isChartEmbeddablesEnabled }) => ($isChartEmbeddablesEnabled ? `66%` : `34%;`)}; width: 100%; max-width: 77px; position: absolute; @@ -78,11 +78,11 @@ export const StyledEuiFlexItem = styled(EuiFlexItem)` `; const DonutChartWrapperComponent: React.FC = ({ + children, dataExists, + isChartEmbeddablesEnabled, label, - children, title, - isChartEmbeddablesEnabled, }) => { const { euiTheme } = useEuiTheme(); const emptyLabelStyle = useMemo( @@ -101,12 +101,11 @@ const DonutChartWrapperComponent: React.FC = ({ > {title} @@ -132,6 +131,7 @@ export const DonutChart = ({ data, fillColor, height = 90, + isChartEmbeddablesEnabled, label, legendItems, onElementClick, @@ -141,7 +141,12 @@ export const DonutChart = ({ const theme = useTheme(); return ( - 0} label={label} title={title}> + 0} + label={label} + title={title} + isChartEmbeddablesEnabled={isChartEmbeddablesEnabled} + > <> {data == null || totalCount == null || totalCount === 0 ? ( diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx index be35d8ecef4750..872faa87b823cc 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx @@ -264,6 +264,7 @@ export const AlertsByStatus = ({ label={STATUS_OPEN} title={} totalCount={openCount} + isChartEmbeddablesEnabled={isChartEmbeddablesEnabled} /> )} @@ -286,6 +287,7 @@ export const AlertsByStatus = ({ label={STATUS_ACKNOWLEDGED} title={} totalCount={acknowledgedCount} + isChartEmbeddablesEnabled={isChartEmbeddablesEnabled} /> )} @@ -305,6 +307,7 @@ export const AlertsByStatus = ({ label={STATUS_CLOSED} title={} totalCount={closedCount} + isChartEmbeddablesEnabled={isChartEmbeddablesEnabled} /> )} From 29da0098f0d0f084bc6fe0bceed99c42ab9b8ff8 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 5 Jan 2023 14:42:32 +0000 Subject: [PATCH 22/72] fix types --- .../rule_details_ui/pages/rule_details/index.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index aecfb356fbccd7..2648804a934933 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -831,10 +831,7 @@ const RuleDetailsPageComponent: React.FC = ({ filters={alertMergedFilters} query={query} runtimeMappings={runtimeMappings} - showBuildingBlockAlerts={showBuildingBlockAlerts} - showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} signalIndexName={signalIndexName} - status={filterGroup} updateDateRange={updateDateRangeCallback} /> From a91bf4f5da393156f5b3903087446ebdda56964c Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 5 Jan 2023 14:50:39 +0000 Subject: [PATCH 23/72] update snapshot --- .../alerts_histogram.test.ts.snap | 553 +----------------- ...s_donut.ts => alerts_by_severity_donut.ts} | 0 .../common/alerts/alerts_histogram.test.ts | 71 +-- .../alert_donut_embeddable.tsx | 2 +- 4 files changed, 31 insertions(+), 595 deletions(-) rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/{alerts_donut.ts => alerts_by_severity_donut.ts} (100%) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap index dabf33958fbb37..3849924878c67c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`getAlertsHistogramLensAttributes should render with extra options - breakdownField 1`] = ` +exports[`getAlertsHistogramLensAttributes should render with extra options - filters 1`] = ` Object { "description": "", "references": Array [ @@ -70,388 +70,6 @@ Object { }, }, "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "host.id", - "negate": false, - "params": Object { - "query": "123", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "host.id": "123", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "_index", - "negate": false, - "params": Array [ - "signal-index", - ], - "type": "phrases", - }, - "query": Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "match_phrase": Object { - "_index": "signal-index", - }, - }, - ], - }, - }, - }, - ], - "internalReferences": Array [], - "query": Object { - "language": "kql", - "query": "host.name: *", - }, - "visualization": Object { - "axisTitlesVisibilitySettings": Object { - "x": false, - "yLeft": false, - "yRight": true, - }, - "layers": Array [ - Object { - "accessors": Array [ - "e09e0380-0740-4105-becc-0a4ca12e3944", - ], - "layerId": "0039eb0c-9a1a-4687-ae54-0f4e239bec75", - "layerType": "data", - "position": "top", - "seriesType": "bar_stacked", - "showGridlines": false, - "splitAccessor": "34919782-4546-43a5-b668-06ac934d3acd", - "xAccessor": "aac9d7d0-13a3-480a-892b-08207a787926", - }, - ], - "legend": Object { - "isInside": true, - "isVisible": true, - "position": "right", - }, - "preferredSeriesType": "bar_stacked", - "title": "Empty XY chart", - "valueLabels": "hide", - "valuesInLegend": true, - "yLeftExtent": Object { - "mode": "full", - }, - "yRightExtent": Object { - "mode": "full", - }, - }, - }, - "title": "Alerts", - "visualizationType": "lnsXY", -} -`; - -exports[`getAlertsHistogramLensAttributes should render with extra options - showBuildingBlockAlerts 1`] = ` -Object { - "description": "", - "references": Array [ - Object { - "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75", - "type": "index-pattern", - }, - ], - "state": Object { - "adHocDataViews": Object {}, - "datasourceStates": Object { - "formBased": Object { - "layers": Object { - "0039eb0c-9a1a-4687-ae54-0f4e239bec75": Object { - "columnOrder": Array [ - "34919782-4546-43a5-b668-06ac934d3acd", - "aac9d7d0-13a3-480a-892b-08207a787926", - "e09e0380-0740-4105-becc-0a4ca12e3944", - ], - "columns": Object { - "34919782-4546-43a5-b668-06ac934d3acd": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", - "params": Object { - "missingBucket": false, - "orderBy": Object { - "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "secondaryFields": Array [], - "size": 1000, - }, - "scale": "ordinal", - "sourceField": "event.category", - }, - "aac9d7d0-13a3-480a-892b-08207a787926": Object { - "dataType": "date", - "isBucketed": true, - "label": "@timestamp", - "operationType": "date_histogram", - "params": Object { - "interval": "auto", - }, - "scale": "interval", - "sourceField": "@timestamp", - }, - "e09e0380-0740-4105-becc-0a4ca12e3944": Object { - "dataType": "number", - "isBucketed": false, - "label": "Count of records", - "operationType": "count", - "scale": "ratio", - "sourceField": "___records___", - }, - }, - "incompleteColumns": Object {}, - }, - }, - }, - }, - "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "host.id", - "negate": false, - "params": Object { - "query": "123", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "host.id": "123", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "_index", - "negate": false, - "params": Array [ - "signal-index", - ], - "type": "phrases", - }, - "query": Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "match_phrase": Object { - "_index": "signal-index", - }, - }, - ], - }, - }, - }, - ], - "internalReferences": Array [], - "query": Object { - "language": "kql", - "query": "host.name: *", - }, - "visualization": Object { - "axisTitlesVisibilitySettings": Object { - "x": false, - "yLeft": false, - "yRight": true, - }, - "layers": Array [ - Object { - "accessors": Array [ - "e09e0380-0740-4105-becc-0a4ca12e3944", - ], - "layerId": "0039eb0c-9a1a-4687-ae54-0f4e239bec75", - "layerType": "data", - "position": "top", - "seriesType": "bar_stacked", - "showGridlines": false, - "splitAccessor": "34919782-4546-43a5-b668-06ac934d3acd", - "xAccessor": "aac9d7d0-13a3-480a-892b-08207a787926", - }, - ], - "legend": Object { - "isInside": true, - "isVisible": true, - "position": "right", - }, - "preferredSeriesType": "bar_stacked", - "title": "Empty XY chart", - "valueLabels": "hide", - "valuesInLegend": true, - "yLeftExtent": Object { - "mode": "full", - }, - "yRightExtent": Object { - "mode": "full", - }, - }, - }, - "title": "Alerts", - "visualizationType": "lnsXY", -} -`; - -exports[`getAlertsHistogramLensAttributes should render with extra options - showOnlyThreatIndicatorAlerts 1`] = ` -Object { - "description": "", - "references": Array [ - Object { - "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75", - "type": "index-pattern", - }, - ], - "state": Object { - "adHocDataViews": Object {}, - "datasourceStates": Object { - "formBased": Object { - "layers": Object { - "0039eb0c-9a1a-4687-ae54-0f4e239bec75": Object { - "columnOrder": Array [ - "34919782-4546-43a5-b668-06ac934d3acd", - "aac9d7d0-13a3-480a-892b-08207a787926", - "e09e0380-0740-4105-becc-0a4ca12e3944", - ], - "columns": Object { - "34919782-4546-43a5-b668-06ac934d3acd": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", - "params": Object { - "missingBucket": false, - "orderBy": Object { - "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "secondaryFields": Array [], - "size": 1000, - }, - "scale": "ordinal", - "sourceField": "event.category", - }, - "aac9d7d0-13a3-480a-892b-08207a787926": Object { - "dataType": "date", - "isBucketed": true, - "label": "@timestamp", - "operationType": "date_histogram", - "params": Object { - "interval": "auto", - }, - "scale": "interval", - "sourceField": "@timestamp", - }, - "e09e0380-0740-4105-becc-0a4ca12e3944": Object { - "dataType": "number", - "isBucketed": false, - "label": "Count of records", - "operationType": "count", - "scale": "ratio", - "sourceField": "___records___", - }, - }, - "incompleteColumns": Object {}, - }, - }, - }, - }, - "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "kibana.alert.rule.type", - "negate": false, - "params": Object { - "query": "threat_match", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "kibana.alert.rule.type": "threat_match", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "host.id", - "negate": false, - "params": Object { - "query": "123", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "host.id": "123", - }, - }, - }, Object { "meta": Object { "alias": null, @@ -459,7 +77,7 @@ Object { "key": "_index", "negate": false, "params": Array [ - "signal-index", + ".alerts-security.alerts-default", ], "type": "phrases", }, @@ -469,163 +87,13 @@ Object { "should": Array [ Object { "match_phrase": Object { - "_index": "signal-index", + "_index": ".alerts-security.alerts-default", }, }, ], }, }, }, - ], - "internalReferences": Array [], - "query": Object { - "language": "kql", - "query": "host.name: *", - }, - "visualization": Object { - "axisTitlesVisibilitySettings": Object { - "x": false, - "yLeft": false, - "yRight": true, - }, - "layers": Array [ - Object { - "accessors": Array [ - "e09e0380-0740-4105-becc-0a4ca12e3944", - ], - "layerId": "0039eb0c-9a1a-4687-ae54-0f4e239bec75", - "layerType": "data", - "position": "top", - "seriesType": "bar_stacked", - "showGridlines": false, - "splitAccessor": "34919782-4546-43a5-b668-06ac934d3acd", - "xAccessor": "aac9d7d0-13a3-480a-892b-08207a787926", - }, - ], - "legend": Object { - "isInside": true, - "isVisible": true, - "position": "right", - }, - "preferredSeriesType": "bar_stacked", - "title": "Empty XY chart", - "valueLabels": "hide", - "valuesInLegend": true, - "yLeftExtent": Object { - "mode": "full", - }, - "yRightExtent": Object { - "mode": "full", - }, - }, - }, - "title": "Alerts", - "visualizationType": "lnsXY", -} -`; - -exports[`getAlertsHistogramLensAttributes should render with extra options - status 1`] = ` -Object { - "description": "", - "references": Array [ - Object { - "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75", - "type": "index-pattern", - }, - ], - "state": Object { - "adHocDataViews": Object {}, - "datasourceStates": Object { - "formBased": Object { - "layers": Object { - "0039eb0c-9a1a-4687-ae54-0f4e239bec75": Object { - "columnOrder": Array [ - "34919782-4546-43a5-b668-06ac934d3acd", - "aac9d7d0-13a3-480a-892b-08207a787926", - "e09e0380-0740-4105-becc-0a4ca12e3944", - ], - "columns": Object { - "34919782-4546-43a5-b668-06ac934d3acd": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", - "params": Object { - "missingBucket": false, - "orderBy": Object { - "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "secondaryFields": Array [], - "size": 1000, - }, - "scale": "ordinal", - "sourceField": "event.category", - }, - "aac9d7d0-13a3-480a-892b-08207a787926": Object { - "dataType": "date", - "isBucketed": true, - "label": "@timestamp", - "operationType": "date_histogram", - "params": Object { - "interval": "auto", - }, - "scale": "interval", - "sourceField": "@timestamp", - }, - "e09e0380-0740-4105-becc-0a4ca12e3944": Object { - "dataType": "number", - "isBucketed": false, - "label": "Count of records", - "operationType": "count", - "scale": "ratio", - "sourceField": "___records___", - }, - }, - "incompleteColumns": Object {}, - }, - }, - }, - }, - "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "kibana.alert.workflow_status", - "negate": false, - "params": Object { - "query": "open", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "kibana.alert.workflow_status": "open", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, Object { "meta": Object { "alias": null, @@ -785,21 +253,6 @@ Object { }, }, "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, Object { "meta": Object { "alias": null, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_donut.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_donut.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_donut.ts rename to x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_donut.ts diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts index 02a9223e801e73..b3ccb0b0f4b733 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts @@ -43,53 +43,36 @@ describe('getAlertsHistogramLensAttributes', () => { expect(result?.current).toMatchSnapshot(); }); - it('should render with extra options - showBuildingBlockAlerts', () => { + it('should render with extra options - filters', () => { const { result } = renderHook( () => useLensAttributes({ - extraOptions: { showBuildingBlockAlerts: true }, - getLensAttributes: getAlertsHistogramLensAttributes, - stackByField: 'event.category', - }), - { wrapper } - ); - - expect(result?.current).toMatchSnapshot(); - }); - - it('should render with extra options - showOnlyThreatIndicatorAlerts', () => { - const { result } = renderHook( - () => - useLensAttributes({ - extraOptions: { showOnlyThreatIndicatorAlerts: true }, - getLensAttributes: getAlertsHistogramLensAttributes, - stackByField: 'event.category', - }), - { wrapper } - ); - - expect(result?.current).toMatchSnapshot(); - }); - - it('should render with extra options - status', () => { - const { result } = renderHook( - () => - useLensAttributes({ - extraOptions: { status: 'open' }, - getLensAttributes: getAlertsHistogramLensAttributes, - stackByField: 'event.category', - }), - { wrapper } - ); - - expect(result?.current).toMatchSnapshot(); - }); - - it('should render with extra options - breakdownField', () => { - const { result } = renderHook( - () => - useLensAttributes({ - extraOptions: { breakdownField: 'agent.type' }, + extraOptions: { + filters: [ + { + meta: { + type: 'phrases', + key: '_index', + params: ['.alerts-security.alerts-default'], + alias: null, + negate: false, + disabled: false, + }, + query: { + bool: { + should: [ + { + match_phrase: { + _index: '.alerts-security.alerts-default', + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + ], + }, getLensAttributes: getAlertsHistogramLensAttributes, stackByField: 'event.category', }), diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx index 80f72934d73f5d..8c0b44cd533783 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import { DonutChartWrapper } from '../../../../common/components/charts/donutchart'; import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; -import { getAlertsBySeverityAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_donut'; +import { getAlertsBySeverityAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_donut'; import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; import type { EmbeddableData } from '../../../../common/components/visualization_actions/types'; import { parseVisualizationData } from '../../../../common/components/visualization_actions/utils'; From 4d4a5cfb179e2759cecfc69f1181bde85d979df7 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 5 Jan 2023 15:16:54 +0000 Subject: [PATCH 24/72] update snapshot --- .../alerts_treemap_panel/index.test.tsx | 4 - .../__snapshots__/alerts_table.test.ts.snap | 416 +----------------- .../__snapshots__/alerts_treemap.test.ts.snap | 414 +---------------- .../common/alerts/alerts_table.test.ts | 57 ++- .../common/alerts/alerts_treemap.test.ts | 57 ++- .../signals_by_category.tsx | 2 - 6 files changed, 60 insertions(+), 890 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx index ee8e0c6b083f65..d89784c3aead82 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx @@ -9,7 +9,6 @@ import { render, screen, waitFor } from '@testing-library/react'; import React from 'react'; import { useLocation } from 'react-router-dom'; -import type { Status } from '../../../../common/detection_engine/schemas/common'; import { SecurityPageName } from '../../../../common/constants'; import { useGlobalTime } from '../../containers/use_global_time'; import { @@ -136,12 +135,9 @@ const defaultProps: Props = { setIsPanelExpanded: jest.fn(), setStackByField0: jest.fn(), setStackByField1: jest.fn(), - showBuildingBlockAlerts: false, - showOnlyThreatIndicatorAlerts: false, signalIndexName: '.alerts-security.alerts-default', stackByField0: 'kibana.alert.rule.name', stackByField1: 'host.name', - status: 'open' as Status, title: , }; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap index 5196780c11dcf0..4b609924b05ea7 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap @@ -93,191 +93,6 @@ Object { "layers": Object {}, }, }, - "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "host.id", - "negate": false, - "params": Object { - "query": "123", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "host.id": "123", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "_index", - "negate": false, - "params": Array [ - "signal-index", - ], - "type": "phrases", - }, - "query": Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "match_phrase": Object { - "_index": "signal-index", - }, - }, - ], - }, - }, - }, - ], - "internalReferences": Array [], - "query": Object { - "language": "kql", - "query": "host.name: *", - }, - "visualization": Object { - "columns": Array [ - Object { - "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166", - "isTransposed": false, - "width": 362, - }, - Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "isTransposed": false, - }, - Object { - "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "isTransposed": false, - }, - ], - "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "layerType": "data", - "paging": Object { - "enabled": true, - "size": 10, - }, - }, - }, - "title": "Alerts", - "visualizationType": "lnsDatatable", -} -`; - -exports[`getAlertsTableLensAttributes should render with extra options - showBuildingBlockAlerts 1`] = ` -Object { - "description": "", - "references": Array [ - Object { - "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "type": "index-pattern", - }, - ], - "state": Object { - "adHocDataViews": Object {}, - "datasourceStates": Object { - "formBased": Object { - "layers": Object { - "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { - "columnOrder": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "columns": Object { - "2881fedd-54b7-42ba-8c97-5175dec86166": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": "event.category", - }, - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of undefined", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": undefined, - }, - "f04a71a3-399f-4d32-9efc-8a005e989991": Object { - "dataType": "number", - "isBucketed": false, - "label": "Count of undefined", - "operationType": "count", - "params": Object { - "emptyAsNull": true, - }, - "scale": "ratio", - "sourceField": undefined, - }, - }, - "incompleteColumns": Object {}, - "sampling": 1, - }, - }, - }, - "textBased": Object { - "layers": Object {}, - }, - }, "filters": Array [ Object { "meta": Object { @@ -355,7 +170,7 @@ Object { } `; -exports[`getAlertsTableLensAttributes should render with extra options - showOnlyThreatIndicatorAlerts 1`] = ` +exports[`getAlertsTableLensAttributes should render with extra options - filters 1`] = ` Object { "description": "", "references": Array [ @@ -449,55 +264,6 @@ Object { }, }, "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "kibana.alert.rule.type", - "negate": false, - "params": Object { - "query": "threat_match", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "kibana.alert.rule.type": "threat_match", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "host.id", - "negate": false, - "params": Object { - "query": "123", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "host.id": "123", - }, - }, - }, Object { "meta": Object { "alias": null, @@ -505,7 +271,7 @@ Object { "key": "_index", "negate": false, "params": Array [ - "signal-index", + ".alerts-security.alerts-default", ], "type": "phrases", }, @@ -515,174 +281,13 @@ Object { "should": Array [ Object { "match_phrase": Object { - "_index": "signal-index", + "_index": ".alerts-security.alerts-default", }, }, ], }, }, }, - ], - "internalReferences": Array [], - "query": Object { - "language": "kql", - "query": "host.name: *", - }, - "visualization": Object { - "columns": Array [ - Object { - "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166", - "isTransposed": false, - "width": 362, - }, - Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "isTransposed": false, - }, - Object { - "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "isTransposed": false, - }, - ], - "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "layerType": "data", - "paging": Object { - "enabled": true, - "size": 10, - }, - }, - }, - "title": "Alerts", - "visualizationType": "lnsDatatable", -} -`; - -exports[`getAlertsTableLensAttributes should render with extra options - status 1`] = ` -Object { - "description": "", - "references": Array [ - Object { - "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "type": "index-pattern", - }, - ], - "state": Object { - "adHocDataViews": Object {}, - "datasourceStates": Object { - "formBased": Object { - "layers": Object { - "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { - "columnOrder": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "columns": Object { - "2881fedd-54b7-42ba-8c97-5175dec86166": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": "event.category", - }, - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of undefined", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": undefined, - }, - "f04a71a3-399f-4d32-9efc-8a005e989991": Object { - "dataType": "number", - "isBucketed": false, - "label": "Count of undefined", - "operationType": "count", - "params": Object { - "emptyAsNull": true, - }, - "scale": "ratio", - "sourceField": undefined, - }, - }, - "incompleteColumns": Object {}, - "sampling": 1, - }, - }, - }, - "textBased": Object { - "layers": Object {}, - }, - }, - "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "kibana.alert.workflow_status", - "negate": false, - "params": Object { - "query": "open", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "kibana.alert.workflow_status": "open", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, Object { "meta": Object { "alias": null, @@ -853,21 +458,6 @@ Object { }, }, "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, Object { "meta": Object { "alias": null, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap index 4a440221a9f08c..981bb217aae6e2 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap @@ -94,21 +94,6 @@ Object { }, }, "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, Object { "meta": Object { "alias": null, @@ -184,7 +169,7 @@ Object { } `; -exports[`getAlertsTreemapLensAttributes should render with extra options - showBuildingBlockAlerts 1`] = ` +exports[`getAlertsTreemapLensAttributes should render with extra options - filters 1`] = ` Object { "description": "", "references": Array [ @@ -278,224 +263,6 @@ Object { }, }, "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "host.id", - "negate": false, - "params": Object { - "query": "123", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "host.id": "123", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "_index", - "negate": false, - "params": Array [ - "signal-index", - ], - "type": "phrases", - }, - "query": Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "match_phrase": Object { - "_index": "signal-index", - }, - }, - ], - }, - }, - }, - ], - "internalReferences": Array [], - "query": Object { - "language": "kql", - "query": "host.name: *", - }, - "visualization": Object { - "layers": Array [ - Object { - "categoryDisplay": "default", - "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "layerType": "data", - "legendDisplay": "show", - "legendPosition": "left", - "legendSize": "xlarge", - "metrics": Array [ - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "nestedLegend": true, - "numberDisplay": "value", - "primaryGroups": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - ], - }, - ], - "shape": "treemap", - }, - }, - "title": "Alerts", - "visualizationType": "lnsPie", -} -`; - -exports[`getAlertsTreemapLensAttributes should render with extra options - showOnlyThreatIndicatorAlerts 1`] = ` -Object { - "description": "", - "references": Array [ - Object { - "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "type": "index-pattern", - }, - ], - "state": Object { - "adHocDataViews": Object {}, - "datasourceStates": Object { - "formBased": Object { - "layers": Object { - "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { - "columnOrder": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "columns": Object { - "2881fedd-54b7-42ba-8c97-5175dec86166": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": "event.category", - }, - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of undefined", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": undefined, - }, - "f04a71a3-399f-4d32-9efc-8a005e989991": Object { - "dataType": "number", - "isBucketed": false, - "label": "Maximum of kibana.alert.risk_score", - "operationType": "max", - "params": Object { - "emptyAsNull": true, - }, - "scale": "ratio", - "sourceField": "kibana.alert.risk_score", - }, - }, - "incompleteColumns": Object {}, - "sampling": 1, - }, - }, - }, - "textBased": Object { - "layers": Object {}, - }, - }, - "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "kibana.alert.rule.type", - "negate": false, - "params": Object { - "query": "threat_match", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "kibana.alert.rule.type": "threat_match", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "host.id", - "negate": false, - "params": Object { - "query": "123", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "host.id": "123", - }, - }, - }, Object { "meta": Object { "alias": null, @@ -503,7 +270,7 @@ Object { "key": "_index", "negate": false, "params": Array [ - "signal-index", + ".alerts-security.alerts-default", ], "type": "phrases", }, @@ -513,173 +280,13 @@ Object { "should": Array [ Object { "match_phrase": Object { - "_index": "signal-index", + "_index": ".alerts-security.alerts-default", }, }, ], }, }, }, - ], - "internalReferences": Array [], - "query": Object { - "language": "kql", - "query": "host.name: *", - }, - "visualization": Object { - "layers": Array [ - Object { - "categoryDisplay": "default", - "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "layerType": "data", - "legendDisplay": "show", - "legendPosition": "left", - "legendSize": "xlarge", - "metrics": Array [ - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "nestedLegend": true, - "numberDisplay": "value", - "primaryGroups": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - ], - }, - ], - "shape": "treemap", - }, - }, - "title": "Alerts", - "visualizationType": "lnsPie", -} -`; - -exports[`getAlertsTreemapLensAttributes should render with extra options - status 1`] = ` -Object { - "description": "", - "references": Array [ - Object { - "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "type": "index-pattern", - }, - ], - "state": Object { - "adHocDataViews": Object {}, - "datasourceStates": Object { - "formBased": Object { - "layers": Object { - "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { - "columnOrder": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "columns": Object { - "2881fedd-54b7-42ba-8c97-5175dec86166": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": "event.category", - }, - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of undefined", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": undefined, - }, - "f04a71a3-399f-4d32-9efc-8a005e989991": Object { - "dataType": "number", - "isBucketed": false, - "label": "Maximum of kibana.alert.risk_score", - "operationType": "max", - "params": Object { - "emptyAsNull": true, - }, - "scale": "ratio", - "sourceField": "kibana.alert.risk_score", - }, - }, - "incompleteColumns": Object {}, - "sampling": 1, - }, - }, - }, - "textBased": Object { - "layers": Object {}, - }, - }, - "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "kibana.alert.workflow_status", - "negate": false, - "params": Object { - "query": "open", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "kibana.alert.workflow_status": "open", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, Object { "meta": Object { "alias": null, @@ -849,21 +456,6 @@ Object { }, }, "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": ".alerts-security.alerts-id", - "key": "kibana.alert.building_block_type", - "negate": true, - "type": "exists", - }, - "query": Object { - "exists": Object { - "field": "kibana.alert.building_block_type", - }, - }, - }, Object { "meta": Object { "alias": null, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts index df6721595ef758..34f9cb45eab403 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts @@ -41,39 +41,36 @@ describe('getAlertsTableLensAttributes', () => { expect(result?.current).toMatchSnapshot(); }); - it('should render with extra options - showBuildingBlockAlerts', () => { + it('should render with extra options - filters', () => { const { result } = renderHook( () => useLensAttributes({ - extraOptions: { showBuildingBlockAlerts: true }, - getLensAttributes: getAlertsTableLensAttributes, - stackByField: 'event.category', - }), - { wrapper } - ); - - expect(result?.current).toMatchSnapshot(); - }); - - it('should render with extra options - showOnlyThreatIndicatorAlerts', () => { - const { result } = renderHook( - () => - useLensAttributes({ - extraOptions: { showOnlyThreatIndicatorAlerts: true }, - getLensAttributes: getAlertsTableLensAttributes, - stackByField: 'event.category', - }), - { wrapper } - ); - - expect(result?.current).toMatchSnapshot(); - }); - - it('should render with extra options - status', () => { - const { result } = renderHook( - () => - useLensAttributes({ - extraOptions: { status: 'open' }, + extraOptions: { + filters: [ + { + meta: { + type: 'phrases', + key: '_index', + params: ['.alerts-security.alerts-default'], + alias: null, + negate: false, + disabled: false, + }, + query: { + bool: { + should: [ + { + match_phrase: { + _index: '.alerts-security.alerts-default', + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + ], + }, getLensAttributes: getAlertsTableLensAttributes, stackByField: 'event.category', }), diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts index 9492d27adfa7a5..af80ae5a3f5b69 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts @@ -41,39 +41,36 @@ describe('getAlertsTreemapLensAttributes', () => { expect(result?.current).toMatchSnapshot(); }); - it('should render with extra options - showBuildingBlockAlerts', () => { + it('should render with extra options - filters', () => { const { result } = renderHook( () => useLensAttributes({ - extraOptions: { showBuildingBlockAlerts: true }, - getLensAttributes: getAlertsTreemapLensAttributes, - stackByField: 'event.category', - }), - { wrapper } - ); - - expect(result?.current).toMatchSnapshot(); - }); - - it('should render with extra options - showOnlyThreatIndicatorAlerts', () => { - const { result } = renderHook( - () => - useLensAttributes({ - extraOptions: { showOnlyThreatIndicatorAlerts: true }, - getLensAttributes: getAlertsTreemapLensAttributes, - stackByField: 'event.category', - }), - { wrapper } - ); - - expect(result?.current).toMatchSnapshot(); - }); - - it('should render with extra options - status', () => { - const { result } = renderHook( - () => - useLensAttributes({ - extraOptions: { status: 'open' }, + extraOptions: { + filters: [ + { + meta: { + type: 'phrases', + key: '_index', + params: ['.alerts-security.alerts-default'], + alias: null, + negate: false, + disabled: false, + }, + query: { + bool: { + should: [ + { + match_phrase: { + _index: '.alerts-security.alerts-default', + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + ], + }, getLensAttributes: getAlertsTreemapLensAttributes, stackByField: 'event.category', }), diff --git a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx index ad8e686edb747c..34946e58520aca 100644 --- a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx @@ -78,10 +78,8 @@ const SignalsByCategoryComponent: React.FC = ({ paddingSize={paddingSize} query={query} runtimeMappings={runtimeMappings} - showBuildingBlockAlerts={false} showLegend={showLegend} showLinkToAlerts={onlyField == null ? true : false} - showOnlyThreatIndicatorAlerts={false} showStackBy={onlyField == null} showTotalAlertsCount={true} signalIndexName={signalIndexName} From 075ef5fb3664ae179f580387ec8110ae8fc08592 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 5 Jan 2023 15:46:55 +0000 Subject: [PATCH 25/72] styling --- .../public/common/components/charts/donutchart.tsx | 6 ++++-- ...lerts_by_severity_donut.ts => alerts_by_status_donut.ts} | 2 +- .../alerts_by_status/alert_donut_embeddable.tsx | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/{alerts_by_severity_donut.ts => alerts_by_status_donut.ts} (98%) diff --git a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx index c19887636a44b8..a3b79fba64324f 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx @@ -63,9 +63,10 @@ export interface DonutChartWrapperProps { /* Make this position absolute in order to overlap the text onto the donut */ export const DonutTextWrapper = styled(EuiFlexGroup)< - EuiFlexGroupProps & { $isChartEmbeddablesEnabled?: boolean } + EuiFlexGroupProps & { $isChartEmbeddablesEnabled?: boolean; $dataExists?: boolean } >` - top: ${({ $isChartEmbeddablesEnabled }) => ($isChartEmbeddablesEnabled ? `66%` : `34%;`)}; + top: ${({ $isChartEmbeddablesEnabled, $dataExists }) => + $isChartEmbeddablesEnabled && !$dataExists ? `66%` : `34%;`}; width: 100%; max-width: 77px; position: absolute; @@ -101,6 +102,7 @@ const DonutChartWrapperComponent: React.FC = ({ > diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx index 8c0b44cd533783..3fdfa7784b85e4 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import { DonutChartWrapper } from '../../../../common/components/charts/donutchart'; import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; -import { getAlertsBySeverityAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_donut'; +import { getAlertsByStatusAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut'; import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; import type { EmbeddableData } from '../../../../common/components/visualization_actions/types'; import { parseVisualizationData } from '../../../../common/components/visualization_actions/utils'; @@ -78,7 +78,7 @@ const AlertDonutEmbeddableComponent: React.FC = ({ height={ChartSize} width={ChartSize} onLoad={onEmbeddableLoad} - getLensAttributes={getAlertsBySeverityAttributes} + getLensAttributes={getAlertsByStatusAttributes} stackByField="kibana.alert.workflow_status" scopeId={SourcererScopeName.detections} id={`alertsStatus${status}`} From d619b30a9179ed8919b0e9b33216749edb28c10c Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sat, 7 Jan 2023 19:19:41 +0000 Subject: [PATCH 26/72] unit tests --- .../common/components/charts/areachart.tsx | 5 +++-- .../common/components/charts/barchart.tsx | 5 +++-- .../common/components/charts/donutchart.tsx | 5 +++-- .../components/matrix_histogram/index.tsx | 5 +++-- .../visualization_actions/__mocks__/index.tsx | 2 -- .../visualization_actions/index.tsx | 5 ++--- .../common/alerts/alerts_by_status_donut.ts | 22 ------------------- .../components/visualization_actions/utils.ts | 2 ++ .../components/stat_items/metric.test.tsx | 1 - .../explore/components/stat_items/metric.tsx | 8 +++---- .../stat_items/metric_embeddable.test.tsx | 1 - .../components/stat_items/stat_items.test.tsx | 1 - .../signals_by_category.tsx | 2 +- 13 files changed, 20 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx index aa5db991bc0889..d458c00558aa60 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx @@ -25,10 +25,11 @@ import { Wrapper, ChartWrapper, } from './common'; -import { VisualizationActions, HISTOGRAM_ACTIONS_BUTTON_CLASS } from '../visualization_actions'; +import { VisualizationActions } from '../visualization_actions'; import type { VisualizationActionsProps } from '../visualization_actions/types'; import { HoverVisibilityContainer } from '../hover_visibility_container'; +import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../visualization_actions/utils'; // custom series styles: https://ela.st/areachart-styling const getSeriesLineStyle = (): RecursivePartial => { @@ -155,7 +156,7 @@ export const AreaChartComponent: React.FC = ({ return ( - + {isValidSeriesExist && areaChart && ( diff --git a/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx index 59c99694e9228f..207aa4236ed094 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx @@ -35,9 +35,10 @@ import { import { DraggableLegend } from './draggable_legend'; import type { LegendItem } from './draggable_legend_item'; import type { ChartData, ChartSeriesConfigs, ChartSeriesData } from './common'; -import { VisualizationActions, HISTOGRAM_ACTIONS_BUTTON_CLASS } from '../visualization_actions'; +import { VisualizationActions } from '../visualization_actions'; import type { VisualizationActionsProps } from '../visualization_actions/types'; import { HoverVisibilityContainer } from '../hover_visibility_container'; +import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../visualization_actions/utils'; const LegendFlexItem = styled(EuiFlexItem)` overview: hidden; @@ -207,7 +208,7 @@ export const BarChartComponent: React.FC = ({ return ( - + {isValidSeriesExist && barChart && ( diff --git a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx index a3b79fba64324f..de7898b2e7296f 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx @@ -92,6 +92,7 @@ const DonutChartWrapperComponent: React.FC = ({ }), [euiTheme.colors.disabled] ); + const className = isChartEmbeddablesEnabled ? undefined : 'eui-textTruncate'; return ( = ({ justifyContent="center" > {title} - + diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index c5e1f7f2144c41..4cfc6a88c24004 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -31,12 +31,13 @@ import type { GlobalTimeArgs } from '../../containers/use_global_time'; import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { InputsModelId } from '../../store/inputs/constants'; import { HoverVisibilityContainer } from '../hover_visibility_container'; -import { HISTOGRAM_ACTIONS_BUTTON_CLASS, VisualizationActions } from '../visualization_actions'; +import { VisualizationActions } from '../visualization_actions'; import type { GetLensAttributes, LensAttributes } from '../visualization_actions/types'; import { useQueryToggle } from '../../containers/query_toggle'; import { LensEmbeddable } from '../visualization_actions/lens_embeddable'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; +import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../visualization_actions/utils'; export type MatrixHistogramComponentProps = MatrixHistogramProps & Omit & { @@ -248,7 +249,7 @@ export const MatrixHistogramComponent: React.FC = <>

; - -export const HISTOGRAM_ACTIONS_BUTTON_CLASS = 'histogram-actions-trigger'; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx index 6e5814de8172ea..16336bbded8260 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx @@ -24,6 +24,7 @@ import { MORE_ACTIONS, OPEN_IN_LENS, } from './translations'; +import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from './utils'; const Wrapper = styled.div` &.viz-actions { @@ -37,8 +38,6 @@ const Wrapper = styled.div` } `; -export const HISTOGRAM_ACTIONS_BUTTON_CLASS = 'histogram-actions-trigger'; - const VisualizationActionsComponent: React.FC = ({ className, getLensAttributes, @@ -195,7 +194,7 @@ const VisualizationActionsComponent: React.FC = ({ () => ( hostName ? [ diff --git a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.test.tsx b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.test.tsx index f7a7b96272fdb7..6582ffba002a6b 100644 --- a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.test.tsx @@ -16,7 +16,6 @@ import type { LensAttributes } from '../../../common/components/visualization_ac jest.mock('../../../common/components/visualization_actions', () => { return { VisualizationActions: () =>
, - HISTOGRAM_ACTIONS_BUTTON_CLASS: 'histogram-actions-trigger', }; }); diff --git a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.tsx b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.tsx index 1cde93e7793ac0..af659d1d076331 100644 --- a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.tsx @@ -8,12 +8,10 @@ import { EuiFlexGroup, EuiIcon } from '@elastic/eui'; import React from 'react'; import type { StatItem } from './types'; import { HoverVisibilityContainer } from '../../../common/components/hover_visibility_container'; -import { - VisualizationActions, - HISTOGRAM_ACTIONS_BUTTON_CLASS, -} from '../../../common/components/visualization_actions'; +import { VisualizationActions } from '../../../common/components/visualization_actions'; import { FlexItem, StatValue } from './utils'; import { getEmptyTagValue } from '../../../common/components/empty_value'; +import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../../../common/components/visualization_actions/utils'; export interface MetricProps { fields: StatItem[]; @@ -51,7 +49,7 @@ const MetricComponent = ({ )} - +

{field.value != null ? field.value.toLocaleString() : getEmptyTagValue()}{' '} diff --git a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric_embeddable.test.tsx b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric_embeddable.test.tsx index 0b7840260493b5..b801102b6739a3 100644 --- a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric_embeddable.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric_embeddable.test.tsx @@ -16,7 +16,6 @@ import type { LensAttributes } from '../../../common/components/visualization_ac jest.mock('../../../common/components/visualization_actions', () => { return { VisualizationActions: () =>

, - HISTOGRAM_ACTIONS_BUTTON_CLASS: 'histogram-actions-trigger', }; }); diff --git a/x-pack/plugins/security_solution/public/explore/components/stat_items/stat_items.test.tsx b/x-pack/plugins/security_solution/public/explore/components/stat_items/stat_items.test.tsx index a677805a50cead..39682dc3301aa9 100644 --- a/x-pack/plugins/security_solution/public/explore/components/stat_items/stat_items.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/stat_items/stat_items.test.tsx @@ -43,7 +43,6 @@ jest.mock('../../../common/components/charts/barchart', () => { jest.mock('../../../common/components/visualization_actions', () => { return { VisualizationActions: () =>
, - HISTOGRAM_ACTIONS_BUTTON_CLASS: 'histogram-actions-trigger', }; }); diff --git a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx index 34946e58520aca..61869643860777 100644 --- a/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/signals_by_category/signals_by_category.tsx @@ -77,12 +77,12 @@ const SignalsByCategoryComponent: React.FC = ({ onlyField={onlyField} paddingSize={paddingSize} query={query} - runtimeMappings={runtimeMappings} showLegend={showLegend} showLinkToAlerts={onlyField == null ? true : false} showStackBy={onlyField == null} showTotalAlertsCount={true} signalIndexName={signalIndexName} + runtimeMappings={runtimeMappings} title={i18n.ALERT_TREND} titleSize={onlyField == null ? 'm' : 's'} updateDateRange={updateDateRangeCallback} From d896cb07428b8050992a40152cdbc4963f3577de Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sat, 7 Jan 2023 19:47:18 +0000 Subject: [PATCH 27/72] remove eslint comment --- .../alerts_by_status/alerts_by_status.tsx | 59 +++---------------- ...use_alerts_by_status_visualization_data.ts | 54 +++++++++++++++++ 2 files changed, 62 insertions(+), 51 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx index 872faa87b823cc..b77f63de9bb343 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx @@ -56,11 +56,7 @@ import { useNavigateToTimeline } from '../hooks/use_navigate_to_timeline'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { AlertDonutEmbeddable } from './alert_donut_embeddable'; -import { inputsSelectors } from '../../../../common/store'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { parseVisualizationData } from '../../../../common/components/visualization_actions/utils'; -import type { VisualizationAlertsByStatusResponse } from './types'; -import { AlertsByStatusQueryId } from './types'; +import { useAlertsByStatusVisualizationData } from './use_alerts_by_status_visualization_data'; const StyledFlexItem = styled(EuiFlexItem)` padding: 0 4px; @@ -91,7 +87,6 @@ const eventKindSignalFilter: EntityFilter = { value: 'signal', }; -// eslint-disable-next-line complexity export const AlertsByStatus = ({ additionalFilters, signalIndexName, @@ -102,38 +97,6 @@ export const AlertsByStatus = ({ const { onClick: goToAlerts, href } = useGetSecuritySolutionLinkProps()({ deepLinkId: SecurityPageName.alerts, }); - const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); - const { inspect: inspectOpenAlerts } = useDeepEqualSelector((state) => - getGlobalQuery(state, AlertsByStatusQueryId.openAlertsQuery) - ); - const { inspect: inspectAcknowledgedAlerts } = useDeepEqualSelector((state) => - getGlobalQuery(state, AlertsByStatusQueryId.acknowledgedAlertsQuery) - ); - const { inspect: inspectClosedAlerts } = useDeepEqualSelector((state) => - getGlobalQuery(state, AlertsByStatusQueryId.closedAlertsQuery) - ); - const visualizationOpenAlertsData = - inspectOpenAlerts != null - ? parseVisualizationData(inspectOpenAlerts?.response)[0] - .hits.total - : 0; - const visualizationAcknowledgedAlertsData = - inspectAcknowledgedAlerts != null - ? parseVisualizationData( - inspectAcknowledgedAlerts?.response - )[0].hits.total - : 0; - const visualizationClosedAlertsData = - inspectClosedAlerts != null - ? parseVisualizationData( - inspectClosedAlerts?.response - )[0].hits.total - : 0; - - const visualizationTotalAlertsData = - visualizationOpenAlertsData + - visualizationAcknowledgedAlertsData + - visualizationClosedAlertsData ?? 0; const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const { to, from, setQuery } = useGlobalTime(); @@ -182,6 +145,10 @@ export const AlertsByStatus = ({ const totalAlerts = loading || donutData == null ? 0 : openCount + acknowledgedCount + closedCount; + const { total: visualizationTotalAlerts } = useAlertsByStatusVisualizationData(); + + const totalAlertsCount = isChartEmbeddablesEnabled ? visualizationTotalAlerts : totalAlerts; + const fillColor: FillColor = useCallback((d: ShapeTreeNode) => { return chartConfigs.find((cfg) => cfg.label === d.dataName)?.color ?? emptyDonutColor; }, []); @@ -225,24 +192,14 @@ export const AlertsByStatus = ({ {totalAlerts !== 0 || - (visualizationTotalAlertsData !== 0 && ( + (visualizationTotalAlerts !== 0 && ( <> - + <> - - {ALERTS( - isChartEmbeddablesEnabled ? visualizationTotalAlertsData : totalAlerts - )} - + {ALERTS(totalAlertsCount)} ))} diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts new file mode 100644 index 00000000000000..9a6109037a60ec --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts @@ -0,0 +1,54 @@ +/* + * 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 { parseVisualizationData } from '../../../../common/components/visualization_actions/utils'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { inputsSelectors } from '../../../../common/store/inputs'; +import type { VisualizationAlertsByStatusResponse } from './types'; +import { AlertsByStatusQueryId } from './types'; + +export const useAlertsByStatusVisualizationData = () => { + const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); + const { inspect: inspectOpenAlerts } = useDeepEqualSelector((state) => + getGlobalQuery(state, AlertsByStatusQueryId.openAlertsQuery) + ); + const { inspect: inspectAcknowledgedAlerts } = useDeepEqualSelector((state) => + getGlobalQuery(state, AlertsByStatusQueryId.acknowledgedAlertsQuery) + ); + const { inspect: inspectClosedAlerts } = useDeepEqualSelector((state) => + getGlobalQuery(state, AlertsByStatusQueryId.closedAlertsQuery) + ); + const visualizationOpenAlertsData = + inspectOpenAlerts != null + ? parseVisualizationData(inspectOpenAlerts?.response)[0] + .hits.total + : 0; + const visualizationAcknowledgedAlertsData = + inspectAcknowledgedAlerts != null + ? parseVisualizationData( + inspectAcknowledgedAlerts?.response + )[0].hits.total + : 0; + const visualizationClosedAlertsData = + inspectClosedAlerts != null + ? parseVisualizationData( + inspectClosedAlerts?.response + )[0].hits.total + : 0; + + const visualizationTotalAlertsData = + visualizationOpenAlertsData + + visualizationAcknowledgedAlertsData + + visualizationClosedAlertsData ?? 0; + + return { + open: visualizationOpenAlertsData, + acknowledged: visualizationAcknowledgedAlertsData, + closed: visualizationClosedAlertsData, + total: visualizationTotalAlertsData, + }; +}; From 82b4ddbc874d263df6efbeb1f46d79e88d3ffacb Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sat, 7 Jan 2023 22:38:44 +0000 Subject: [PATCH 28/72] refetch --- .../alert_donut_embeddable.tsx | 49 +++++++++++-------- .../alerts_by_status/alerts_by_status.tsx | 19 ++++--- .../alerts_by_status/types.ts | 8 +-- .../use_alerts_by_status.test.tsx | 2 + .../alerts_by_status/use_alerts_by_status.ts | 32 ++++++++++-- ...use_alerts_by_status_visualization_data.ts | 8 +-- 6 files changed, 73 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx index 3fdfa7784b85e4..9ebac08c8b363e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx @@ -5,6 +5,7 @@ * 2.0. */ import React, { useCallback, useEffect, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; import { DonutChartWrapper } from '../../../../common/components/charts/donutchart'; import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; import { getAlertsByStatusAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut'; @@ -12,22 +13,22 @@ import { LensEmbeddable } from '../../../../common/components/visualization_acti import type { EmbeddableData } from '../../../../common/components/visualization_actions/types'; import { parseVisualizationData } from '../../../../common/components/visualization_actions/utils'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { inputsSelectors } from '../../../../common/store/inputs'; +import { inputsActions, inputsSelectors } from '../../../../common/store/inputs'; import { InputsModelId } from '../../../../common/store/inputs/constants'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { ChartLabel } from './chart_label'; import type { AlertDonutEmbeddableProps, VisualizationAlertsByStatusResponse } from './types'; -import { AlertsByStatusQueryId } from './types'; +import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from './types'; const ChartSize = '135px'; const AlertDonutEmbeddableComponent: React.FC = ({ status, - setQuery, timerange, label, }) => { - const queryId = AlertsByStatusQueryId[`${status}AlertsQuery`]; + const dispatch = useDispatch(); + const queryId = `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-${status}`; const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ inputId: InputsModelId.global, queryId, @@ -40,28 +41,34 @@ const AlertDonutEmbeddableComponent: React.FC = ({ const onEmbeddableLoad = useCallback( ({ requests, responses, isLoading }: EmbeddableData) => { - setQuery({ - id: queryId, - searchSessionId, - refetch: refetchByRestartingSession, - loading: isLoading, - inspect: { dsl: requests, response: responses }, - }); + dispatch( + inputsActions.setQuery({ + inputId: InputsModelId.global, + id: queryId, + searchSessionId, + refetch: refetchByRestartingSession, + loading: isLoading, + inspect: { dsl: requests, response: responses }, + }) + ); }, - [queryId, refetchByRestartingSession, searchSessionId, setQuery] + [dispatch, queryId, refetchByRestartingSession, searchSessionId] ); const extraOptions = useMemo(() => ({ status }), [status]); useEffect(() => { - setQuery({ - id: `alertsStatus${status}`, - searchSessionId, - refetch: refetchByRestartingSession, - loading: false, - inspect: null, - }); - }, [refetchByRestartingSession, searchSessionId, setQuery, status]); + dispatch( + inputsActions.setQuery({ + inputId: InputsModelId.global, + id: queryId, + searchSessionId, + refetch: refetchByRestartingSession, + loading: false, + inspect: null, + }) + ); + }, [dispatch, queryId, refetchByRestartingSession, searchSessionId, status]); const dataExists = visualizationData != null && visualizationData[0].hits.total !== 0; @@ -81,7 +88,7 @@ const AlertDonutEmbeddableComponent: React.FC = ({ getLensAttributes={getAlertsByStatusAttributes} stackByField="kibana.alert.workflow_status" scopeId={SourcererScopeName.detections} - id={`alertsStatus${status}`} + id={queryId} /> ); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx index b77f63de9bb343..1032dd2d4e95c4 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx @@ -57,6 +57,7 @@ import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_ex import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { AlertDonutEmbeddable } from './alert_donut_embeddable'; import { useAlertsByStatusVisualizationData } from './use_alerts_by_status_visualization_data'; +import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from './types'; const StyledFlexItem = styled(EuiFlexItem)` padding: 0 4px; @@ -68,9 +69,9 @@ const StyledLegendFlexItem = styled(EuiFlexItem)` `; interface AlertsByStatusProps { - signalIndexName: string | null; - entityFilter?: EntityFilter; additionalFilters?: ESBoolQuery[]; + entityFilter?: EntityFilter; + signalIndexName: string | null; } const legendField = 'kibana.alert.severity'; @@ -80,7 +81,6 @@ const chartConfigs: Array<{ key: Severity; label: string; color: string }> = [ { key: 'medium', label: STATUS_MEDIUM_LABEL, color: SEVERITY_COLOR.medium }, { key: 'low', label: STATUS_LOW_LABEL, color: SEVERITY_COLOR.low }, ]; -const DETECTION_RESPONSE_ALERTS_BY_STATUS_ID = 'detection-response-alerts-by-status'; const eventKindSignalFilter: EntityFilter = { field: 'event.kind', @@ -99,7 +99,7 @@ export const AlertsByStatus = ({ }); const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); - const { to, from, setQuery } = useGlobalTime(); + const { to, from } = useGlobalTime(); const timerange = useMemo(() => ({ from, to }), [from, to]); const isLargerBreakpoint = useIsWithinMinBreakpoint('xl'); @@ -127,6 +127,8 @@ export const AlertsByStatus = ({ signalIndexName, skip: !toggleStatus || isChartEmbeddablesEnabled, queryId: DETECTION_RESPONSE_ALERTS_BY_STATUS_ID, + to, + from, }); const legendItems: LegendItem[] = useMemo( () => @@ -208,8 +210,7 @@ export const AlertsByStatus = ({ {isChartEmbeddablesEnabled ? ( @@ -231,8 +232,7 @@ export const AlertsByStatus = ({ > {isChartEmbeddablesEnabled ? ( @@ -251,8 +251,7 @@ export const AlertsByStatus = ({ {isChartEmbeddablesEnabled ? ( diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts index fe159943280fca..7310e3ea3716e3 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts @@ -7,7 +7,6 @@ import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import type { ESQuery } from '../../../../../common/typed_json'; -import type { GlobalTimeArgs } from '../../../../common/containers/use_global_time'; interface StatusBySeverity { doc_count_error_upper_bound: number; @@ -80,7 +79,6 @@ export type AlertsByStatus = 'open' | 'acknowledged' | 'closed'; export interface AlertDonutEmbeddableProps { status: AlertsByStatus; - setQuery: GlobalTimeArgs['setQuery']; timerange: { from: string; to: string }; label: string; } @@ -95,8 +93,4 @@ export interface VisualizationInspectQuery { response: VisualizationAlertsByStatusResponse[]; } -export enum AlertsByStatusQueryId { - 'openAlertsQuery' = 'openAlertsQuery', - 'acknowledgedAlertsQuery' = 'acknowledgedAlertsQuery', - 'closedAlertsQuery' = 'closedAlertsQuery', -} +export const DETECTION_RESPONSE_ALERTS_BY_STATUS_ID = 'detection-response-alerts-by-status'; diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx index 4cbaa8bcff4c27..1169262bb42930 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx @@ -47,6 +47,8 @@ const renderUseAlertsByStatus = (props: Partial = {}) => useAlertsByStatus({ queryId: 'test', signalIndexName: 'signal-alerts', + from: '2023-01-07T00:00:00.000Z', + to: '2023-01-07T23:59:59.999Z', ...props, }), { diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.ts index ee7545f3f90237..e80f3ec0a0caf2 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.ts @@ -8,8 +8,8 @@ import { useCallback, useEffect, useState } from 'react'; import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; +import { useDispatch } from 'react-redux'; import type { ESBoolQuery } from '../../../../../common/typed_json'; -import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { useQueryAlerts } from '../../../../detections/containers/detection_engine/alerts/use_query'; import { ALERTS_QUERY_NAMES } from '../../../../detections/containers/detection_engine/alerts/constants'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; @@ -20,6 +20,9 @@ import { STATUS_LOW_LABEL, STATUS_MEDIUM_LABEL, } from '../translations'; +import { inputsActions } from '../../../../common/store/inputs'; +import type { DeleteQuery, SetQuery } from '../../../../common/containers/use_global_time/types'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; export const severityLabels: Record = { critical: STATUS_CRITICAL_LABEL, @@ -110,6 +113,8 @@ export interface UseAlertsByStatusProps { skip?: boolean; entityFilter?: EntityFilter; additionalFilters?: ESBoolQuery[]; + from: string; + to: string; } export type UseAlertsByStatus = (props: UseAlertsByStatusProps) => { @@ -124,11 +129,32 @@ export const useAlertsByStatus: UseAlertsByStatus = ({ queryId, signalIndexName, skip = false, + to, + from, }) => { - const { to, from, deleteQuery, setQuery } = useGlobalTime(); + const dispatch = useDispatch(); const [updatedAt, setUpdatedAt] = useState(Date.now()); const [items, setItems] = useState(null); - + const setQuery = useCallback( + ({ id, inspect, loading, refetch, searchSessionId }: SetQuery) => + dispatch( + inputsActions.setQuery({ + inputId: InputsModelId.global, + id, + inspect, + loading, + refetch, + searchSessionId, + }) + ), + [dispatch] + ); + + const deleteQuery = useCallback( + ({ id }: DeleteQuery) => + dispatch(inputsActions.deleteOneQuery({ inputId: InputsModelId.global, id })), + [dispatch] + ); const { data, loading: isLoading, diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts index 9a6109037a60ec..de58feecdabac6 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts @@ -9,18 +9,18 @@ import { parseVisualizationData } from '../../../../common/components/visualizat import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { inputsSelectors } from '../../../../common/store/inputs'; import type { VisualizationAlertsByStatusResponse } from './types'; -import { AlertsByStatusQueryId } from './types'; +import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from './types'; export const useAlertsByStatusVisualizationData = () => { const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); const { inspect: inspectOpenAlerts } = useDeepEqualSelector((state) => - getGlobalQuery(state, AlertsByStatusQueryId.openAlertsQuery) + getGlobalQuery(state, `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-open`) ); const { inspect: inspectAcknowledgedAlerts } = useDeepEqualSelector((state) => - getGlobalQuery(state, AlertsByStatusQueryId.acknowledgedAlertsQuery) + getGlobalQuery(state, `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-acknowledged`) ); const { inspect: inspectClosedAlerts } = useDeepEqualSelector((state) => - getGlobalQuery(state, AlertsByStatusQueryId.closedAlertsQuery) + getGlobalQuery(state, `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-closed`) ); const visualizationOpenAlertsData = inspectOpenAlerts != null From d2023fa333e431e5712494949baa56e168652b70 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sat, 7 Jan 2023 23:32:23 +0000 Subject: [PATCH 29/72] fetch session id only when vis inabled --- .../components/matrix_histogram/index.tsx | 1 + .../page/use_refetch_by_session.tsx | 31 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index 4cfc6a88c24004..724da1e1e512e0 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -172,6 +172,7 @@ export const MatrixHistogramComponent: React.FC = const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ inputId: InputsModelId.global, queryId: id, + skip: !isChartEmbeddablesEnabled, }); const matrixHistogramRequest = { diff --git a/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx b/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx index d1bd6020fb1316..23c95449f28eed 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { useCallback, useRef, useEffect, useMemo } from 'react'; +import { useCallback, useRef, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { useKibana } from '../../lib/kibana'; @@ -23,21 +23,28 @@ interface UseRefetchByRestartingSessionProps { export const useRefetchByRestartingSession = ({ inputId, queryId, + skip, }: UseRefetchByRestartingSessionProps): { - searchSessionId: string; + searchSessionId: string | undefined; refetchByRestartingSession: Refetch; } => { const dispatch = useDispatch(); const { data } = useKibana().services; const session = useRef(data.search.session); - const searchSessionId = useMemo(() => session.current.start(), [session]); + const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); const getTimelineQuery = inputsSelectors.timelineQueryByIdSelector(); - const { selectedInspectIndex } = useDeepEqualSelector((state) => - inputId === InputsModelId.global - ? getGlobalQuery(state, queryId) - : getTimelineQuery(state, queryId) + const { selectedInspectIndex, searchSessionId: existingSearchSessionId } = useDeepEqualSelector( + (state) => + inputId === InputsModelId.global + ? getGlobalQuery(state, queryId) + : getTimelineQuery(state, queryId) + ); + + const searchSessionId = useMemo( + () => (skip ? undefined : existingSearchSessionId ?? session.current.start()), + [existingSearchSessionId, skip] ); const refetchByRestartingSession = useCallback(() => { @@ -51,16 +58,10 @@ export const useRefetchByRestartingSession = ({ * like most of our components, it refetches when receiving a new search * session ID. **/ - searchSessionId: session.current.start(), + searchSessionId: skip ? undefined : session.current.start(), }) ); - }, [dispatch, queryId, selectedInspectIndex]); - - useEffect(() => { - return () => { - data.search.session.clear(); - }; - }); + }, [dispatch, queryId, selectedInspectIndex, skip]); return { searchSessionId, refetchByRestartingSession }; }; From 0891f07957b956dbdfe176fe9ec180a003eb7248 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sat, 7 Jan 2023 23:39:27 +0000 Subject: [PATCH 30/72] unit test --- .../alerts_by_status/use_alerts_by_status.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx index 1169262bb42930..15f933754b68e4 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx @@ -47,8 +47,8 @@ const renderUseAlertsByStatus = (props: Partial = {}) => useAlertsByStatus({ queryId: 'test', signalIndexName: 'signal-alerts', - from: '2023-01-07T00:00:00.000Z', - to: '2023-01-07T23:59:59.999Z', + from, + to, ...props, }), { From 119dbe7dfff593fcedd4dd980ef229278e992663 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sun, 8 Jan 2023 10:02:57 +0000 Subject: [PATCH 31/72] fix types --- .../common/components/page/use_refetch_by_session.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.test.tsx b/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.test.tsx index 8611a9a43c67ec..58992fd098a1cb 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.test.tsx @@ -60,7 +60,7 @@ describe(`useRefetchByRestartingSession`, () => { children: React.ReactNode; }, { - searchSessionId: string; + searchSessionId: string | undefined; refetchByRestartingSession: Refetch; } >; From 010f93b2060d741d245e6883ad12f4dd1ab65c51 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 8 Dec 2022 15:15:43 +0000 Subject: [PATCH 32/72] render histogram with lens --- .../alerts_treemap_panel/index.test.tsx | 87 +++ .../components/alerts_treemap_panel/index.tsx | 85 ++- .../common/components/charts/areachart.tsx | 5 +- .../common/components/charts/barchart.tsx | 5 +- .../common/components/charts/donutchart.tsx | 92 ++- .../events_tab/events_query_tab_body.test.tsx | 3 + .../events_tab/histogram_configurations.ts | 2 +- .../common/components/inspect/modal.tsx | 11 +- .../matrix_histogram/index.test.tsx | 101 +--- .../components/matrix_histogram/index.tsx | 62 +- .../page/use_refetch_by_session.test.tsx | 2 +- .../page/use_refetch_by_session.tsx | 31 +- .../visualization_actions/__mocks__/index.tsx | 9 + .../__mocks__/lens_embeddable.tsx | 9 + .../visualization_actions/index.tsx | 5 +- .../__snapshots__/event.test.ts.snap | 9 +- .../alerts_histogram.test.ts.snap | 343 +++++++++++ .../__snapshots__/alerts_table.test.ts.snap | 535 ++++++++++++++++++ .../__snapshots__/alerts_treemap.test.ts.snap | 532 +++++++++++++++++ .../common/alerts/alerts_by_status_donut.ts | 149 +++++ .../common/alerts/alerts_histogram.test.ts | 84 +++ .../common/alerts/alerts_histogram.ts | 125 ++++ .../common/alerts/alerts_table.test.ts | 96 ++++ .../common/alerts/alerts_table.ts | 136 +++++ .../common/alerts/alerts_treemap.test.ts | 96 ++++ .../common/alerts/alerts_treemap.ts | 133 +++++ .../common/alerts/rule_preview.ts | 162 ++++++ .../common/authentication.test.ts | 1 + .../{hosts => common}/event.test.ts | 1 + .../{hosts => common}/events.ts | 13 +- .../common/external_alert.test.ts | 1 + .../hosts/kpi_host_area.test.ts | 1 + .../hosts/kpi_host_metric.test.ts | 1 + .../hosts/kpi_unique_ips_area.test.ts | 1 + .../hosts/kpi_unique_ips_bar.test.ts | 1 + .../kpi_unique_ips_destination_metric.test.ts | 1 + .../kpi_unique_ips_source_metric.test.ts | 1 + .../network/dns_top_domains.test.ts | 1 + .../network/kpi_dns_queries.test.ts | 1 + .../network/kpi_network_events.test.ts | 1 + .../network/kpi_tls_handshakes.test.ts | 1 + .../network/kpi_unique_flow_ids.test.ts | 1 + .../kpi_unique_private_ips_area.test.ts | 1 + .../kpi_unique_private_ips_bar.test.ts | 1 + ...que_private_ips_destination_metric.test.ts | 1 + ...i_unique_private_ips_source_metric.test.ts | 1 + .../users/kpi_total_users_area.test.ts | 1 + .../users/kpi_total_users_metric.test.ts | 1 + ...user_authentication_metric_failure.test.ts | 1 + .../kpi_user_authentications_area.test.ts | 1 + .../kpi_user_authentications_bar.test.ts | 1 + ...ser_authentications_metric_success.test.ts | 1 + .../visualization_actions/lens_embeddable.tsx | 96 +++- .../components/visualization_actions/types.ts | 34 +- .../visualization_actions/use_actions.ts | 7 +- .../use_lens_attributes.test.tsx | 35 +- .../use_lens_attributes.tsx | 51 +- .../components/visualization_actions/utils.ts | 15 +- .../containers/matrix_histogram/index.ts | 5 +- .../pages/rule_details/index.tsx | 4 +- .../alerts_count_panel/index.test.tsx | 100 +++- .../alerts_kpis/alerts_count_panel/index.tsx | 79 ++- .../alerts_histogram_panel/index.test.tsx | 80 ++- .../alerts_histogram_panel/index.tsx | 98 +++- .../components/alerts_kpis/common/hooks.ts | 5 +- .../rules/rule_preview/preview_histogram.tsx | 54 +- .../rule_preview/use_preview_histogram.tsx | 6 +- .../chart_panels/index.test.tsx | 13 +- .../detection_engine/chart_panels/index.tsx | 57 +- .../detection_engine.test.tsx | 8 + .../components/stat_items/metric.test.tsx | 1 - .../explore/components/stat_items/metric.tsx | 8 +- .../stat_items/metric_embeddable.test.tsx | 1 - .../components/stat_items/stat_items.test.tsx | 1 - .../authentications_query_tab_body.test.tsx | 3 + .../alert_donut_embeddable.tsx | 97 ++++ .../alerts_by_status/alerts_by_status.tsx | 140 +++-- .../alerts_by_status/types.ts | 41 +- .../use_alerts_by_status.test.tsx | 2 + .../alerts_by_status/use_alerts_by_status.ts | 32 +- ...use_alerts_by_status_visualization_data.ts | 54 ++ .../public/overview/pages/overview.test.tsx | 2 + 82 files changed, 3683 insertions(+), 392 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/index.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/lens_embeddable.tsx rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/{hosts => common}/__snapshots__/event.test.ts.snap (96%) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/{hosts => common}/event.test.ts (98%) rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/{hosts => common}/events.ts (89%) create mode 100644 x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx create mode 100644 x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx index 0db21ee27ea75d..d89784c3aead82 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx @@ -23,6 +23,9 @@ import { TestProviders } from '../../mock/test_providers'; import type { Props } from '.'; import { AlertsTreemapPanel } from '.'; import { mockAlertSearchResponse } from '../alerts_treemap/lib/mocks/mock_alert_search_response'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; +import { useInspectButton } from '../../../detections/components/alerts_kpis/common/hooks'; +import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; const from = '2022-07-28T08:20:18.966Z'; const to = '2022-07-28T08:20:18.966Z'; @@ -53,6 +56,21 @@ jest.mock('../../../detections/containers/detection_engine/alerts/use_query', () useQueryAlerts: jest.fn(), })); +jest.mock('../../hooks/use_experimental_features'); +jest.mock('../page/use_refetch_by_session'); +jest.mock('../visualization_actions/lens_embeddable'); + +jest.mock('../page/use_refetch_by_session', () => ({ + useRefetchByRestartingSession: jest.fn().mockReturnValue({ + searchSessionId: 'mockSearchSessionId', + refetchByRestartingSession: jest.fn(), + }), +})); +jest.mock('../../../detections/components/alerts_kpis/common/hooks', () => ({ + useInspectButton: jest.fn(), + useStackByFields: jest.fn(), +})); + const defaultProps: Props = { addFilter: jest.fn(), alignHeader: 'flexStart', @@ -139,6 +157,8 @@ describe('AlertsTreemapPanel', () => { request: '', refetch: () => {}, }); + + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); }); it('renders the panel', async () => { @@ -308,3 +328,70 @@ describe('AlertsTreemapPanel', () => { await waitFor(() => expect(screen.getByTestId('treemap')).toBeInTheDocument()); }); }); + +describe('when isChartEmbeddablesEnabled = true', () => { + const mockSearchSessionId = 'mockSearchSessionId'; + const mockRefetchByRestartingSession = jest.fn(); + const mockRefetch = () => {}; + + beforeEach(() => { + jest.clearAllMocks(); + + (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ + searchSessionId: mockSearchSessionId, + refetchByRestartingSession: mockRefetchByRestartingSession, + }); + + (useLocation as jest.Mock).mockReturnValue([ + { pageName: SecurityPageName.alerts, detailName: undefined }, + ]); + + (useQueryAlerts as jest.Mock).mockReturnValue({ + loading: false, + data: mockAlertSearchResponse, + setQuery: () => {}, + response: '', + request: '', + refetch: mockRefetch, + }); + + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + }); + + it('refetch data by refetchByRestartingSession', async () => { + render( + + + + ); + + await waitFor(() => { + expect((useInspectButton as jest.Mock).mock.calls[0][0].refetch).toEqual( + mockRefetchByRestartingSession + ); + expect((useInspectButton as jest.Mock).mock.calls[0][0].searchSessionId).toEqual( + mockSearchSessionId + ); + }); + }); + + it('renders LensEmbeddable', async () => { + render( + + + + ); + + await waitFor(() => expect(screen.getByTestId('lens-embeddable')).toBeInTheDocument()); + }); + + it('should skip calling getAlertsRiskQuery', async () => { + render( + + + + ); + + await waitFor(() => expect((useQueryAlerts as jest.Mock).mock.calls[0][0].skip).toBeTruthy()); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index 75c78a3be86413..9f558951611f1c 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -6,6 +6,7 @@ */ import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; +import type { Action } from '@kbn/ui-actions-plugin/public'; import type { EuiComboBox } from '@elastic/eui'; import { EuiProgress } from '@elastic/eui'; import type { Filter, Query } from '@kbn/es-query'; @@ -24,6 +25,12 @@ import { HeaderSection } from '../header_section'; import { InspectButtonContainer } from '../inspect'; import { DEFAULT_STACK_BY_FIELD0_SIZE, getAlertsRiskQuery } from '../alerts_treemap/query'; import type { AlertsTreeMapAggregation } from '../alerts_treemap/types'; +import { InputsModelId } from '../../store/inputs/constants'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; +import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; +import { LensEmbeddable } from '../visualization_actions/lens_embeddable'; +import { getAlertsTreemapLensAttributes as getLensAttributes } from '../visualization_actions/lens_attributes/common/alerts/alerts_treemap'; +import { SourcererScopeName } from '../../store/sourcerer/model'; const DEFAULT_HEIGHT = DEFAULT_MIN_CHART_HEIGHT + 134; // px @@ -35,10 +42,11 @@ export interface Props { addFilter?: ({ field, value }: { field: string; value: string | number }) => void; alignHeader?: 'center' | 'baseline' | 'stretch' | 'flexStart' | 'flexEnd'; chartOptionsContextMenu?: (queryId: string) => React.ReactNode; - inspectTitle: string; - isPanelExpanded: boolean; + extraActions?: Action[]; filters?: Filter[]; height?: number; + inspectTitle: string; + isPanelExpanded: boolean; query?: Query; riskSubAggregationField: string; runtimeMappings?: MappingRuntimeFields; @@ -60,10 +68,11 @@ const AlertsTreemapPanelComponent: React.FC = ({ addFilter, alignHeader, chartOptionsContextMenu, - inspectTitle, - isPanelExpanded, + extraActions, filters, height = DEFAULT_HEIGHT, + inspectTitle, + isPanelExpanded, query, riskSubAggregationField, runtimeMappings, @@ -84,6 +93,19 @@ const AlertsTreemapPanelComponent: React.FC = ({ // create a unique, but stable (across re-renders) query id const uniqueQueryId = useMemo(() => `${ALERTS_TREEMAP_ID}-${uuid.v4()}`, []); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const timerange = useMemo(() => ({ from, to }), [from, to]); + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: uniqueQueryId, + }); + const extraVisualizationOptions = useMemo( + () => ({ + breakdownField: stackByField1, + filters, + }), + [stackByField1, filters] + ); const additionalFilters = useMemo(() => { try { @@ -116,7 +138,7 @@ const AlertsTreemapPanelComponent: React.FC = ({ stackByField1, to, }), - skip: !isPanelExpanded, + skip: !isPanelExpanded || isChartEmbeddablesEnabled, indexName: signalIndexName, queryName: ALERTS_QUERY_NAMES.TREE_MAP, }); @@ -147,10 +169,11 @@ const AlertsTreemapPanelComponent: React.FC = ({ useInspectButton({ deleteQuery, loading: isLoadingAlerts, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + request, response, + searchSessionId, setQuery, - refetch, - request, uniqueQueryId, }); @@ -178,7 +201,9 @@ const AlertsTreemapPanelComponent: React.FC = ({ > {isPanelExpanded && ( = ({ )} - {isLoadingAlerts && isPanelExpanded ? ( - - ) : ( - <> - {alertsData != null && isPanelExpanded && ( - - )} - - )} + {isPanelExpanded ? ( + isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( + + ) : isLoadingAlerts ? ( + + ) : ( + <> + {alertsData != null && isPanelExpanded && ( + + )} + + ) + ) : null} ); diff --git a/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx index aa5db991bc0889..d458c00558aa60 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/areachart.tsx @@ -25,10 +25,11 @@ import { Wrapper, ChartWrapper, } from './common'; -import { VisualizationActions, HISTOGRAM_ACTIONS_BUTTON_CLASS } from '../visualization_actions'; +import { VisualizationActions } from '../visualization_actions'; import type { VisualizationActionsProps } from '../visualization_actions/types'; import { HoverVisibilityContainer } from '../hover_visibility_container'; +import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../visualization_actions/utils'; // custom series styles: https://ela.st/areachart-styling const getSeriesLineStyle = (): RecursivePartial => { @@ -155,7 +156,7 @@ export const AreaChartComponent: React.FC = ({ return ( - + {isValidSeriesExist && areaChart && ( diff --git a/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx index 59c99694e9228f..207aa4236ed094 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/barchart.tsx @@ -35,9 +35,10 @@ import { import { DraggableLegend } from './draggable_legend'; import type { LegendItem } from './draggable_legend_item'; import type { ChartData, ChartSeriesConfigs, ChartSeriesData } from './common'; -import { VisualizationActions, HISTOGRAM_ACTIONS_BUTTON_CLASS } from '../visualization_actions'; +import { VisualizationActions } from '../visualization_actions'; import type { VisualizationActionsProps } from '../visualization_actions/types'; import { HoverVisibilityContainer } from '../hover_visibility_container'; +import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../visualization_actions/utils'; const LegendFlexItem = styled(EuiFlexItem)` overview: hidden; @@ -207,7 +208,7 @@ export const BarChartComponent: React.FC = ({ return ( - + {isValidSeriesExist && barChart && ( diff --git a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx index 499ac862c6375f..de7898b2e7296f 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { EuiFlexGroupProps } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip, useEuiTheme } from '@elastic/eui'; import React, { useMemo } from 'react'; @@ -44,38 +45,46 @@ export interface DonutChartProps { data: DonutChartData[] | null | undefined; fillColor: FillColor; height?: number; + isChartEmbeddablesEnabled?: boolean; label: React.ReactElement | string; legendItems?: LegendItem[] | null | undefined; + onElementClick?: ElementClickListener; title: React.ReactElement | string | number | null; totalCount: number | null | undefined; - onElementClick?: ElementClickListener; +} + +export interface DonutChartWrapperProps { + children?: React.ReactElement; + dataExists: boolean; + label: React.ReactElement | string; + title: React.ReactElement | string | number | null; + isChartEmbeddablesEnabled?: boolean; } /* Make this position absolute in order to overlap the text onto the donut */ -const DonutTextWrapper = styled(EuiFlexGroup)` - top: 34%; +export const DonutTextWrapper = styled(EuiFlexGroup)< + EuiFlexGroupProps & { $isChartEmbeddablesEnabled?: boolean; $dataExists?: boolean } +>` + top: ${({ $isChartEmbeddablesEnabled, $dataExists }) => + $isChartEmbeddablesEnabled && !$dataExists ? `66%` : `34%;`}; width: 100%; max-width: 77px; position: absolute; z-index: 1; `; -const StyledEuiFlexItem = styled(EuiFlexItem)` +export const StyledEuiFlexItem = styled(EuiFlexItem)` position: relative; align-items: center; `; -export const DonutChart = ({ - data, - fillColor, - height = 90, +const DonutChartWrapperComponent: React.FC = ({ + children, + dataExists, + isChartEmbeddablesEnabled, label, - legendItems, title, - totalCount, - onElementClick, -}: DonutChartProps) => { - const theme = useTheme(); +}) => { const { euiTheme } = useEuiTheme(); const emptyLabelStyle = useMemo( () => ({ @@ -83,7 +92,7 @@ export const DonutChart = ({ }), [euiTheme.colors.disabled] ); - + const className = isChartEmbeddablesEnabled ? undefined : 'eui-textTruncate'; return ( - + {title} - + {label} + {children} + + + ); +}; +export const DonutChartWrapper = React.memo(DonutChartWrapperComponent); + +export const DonutChart = ({ + data, + fillColor, + height = 90, + isChartEmbeddablesEnabled, + label, + legendItems, + onElementClick, + title, + totalCount, +}: DonutChartProps) => { + const theme = useTheme(); + + return ( + 0} + label={label} + title={title} + isChartEmbeddablesEnabled={isChartEmbeddablesEnabled} + > + <> {data == null || totalCount == null || totalCount === 0 ? ( ) : ( @@ -135,12 +174,13 @@ export const DonutChart = ({ /> )} - - {legendItems && legendItems?.length > 0 && ( - - - - )} - + + {legendItems && legendItems?.length > 0 && ( + + + + )} + + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx index 6737fdf2c525c7..652d8ddb8deee7 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx @@ -47,6 +47,9 @@ jest.mock('../../lib/kibana', () => { }; }); +jest.mock('../visualization_actions'); +jest.mock('../visualization_actions/lens_embeddable'); + jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useHistory: () => mockHistory, diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/histogram_configurations.ts b/x-pack/plugins/security_solution/public/common/components/events_tab/histogram_configurations.ts index b1e23d518c6b6f..f341f090710a04 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_tab/histogram_configurations.ts +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/histogram_configurations.ts @@ -8,7 +8,7 @@ import numeral from '@elastic/numeral'; import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution'; import { getExternalAlertLensAttributes } from '../visualization_actions/lens_attributes/common/external_alert'; -import { getEventsHistogramLensAttributes } from '../visualization_actions/lens_attributes/hosts/events'; +import { getEventsHistogramLensAttributes } from '../visualization_actions/lens_attributes/common/events'; import type { MatrixHistogramConfigs, MatrixHistogramOption } from '../matrix_histogram/types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx index e46c2bca723f24..b9e27c921704ab 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx @@ -45,6 +45,7 @@ const DescriptionListStyled = styled(EuiDescriptionList)` DescriptionListStyled.displayName = 'DescriptionListStyled'; export interface ModalInspectProps { + adHocDataViews?: string[] | null; additionalRequests?: string[] | null; additionalResponses?: string[] | null; closeModal: () => void; @@ -108,6 +109,7 @@ export const formatIndexPatternRequested = (indices: string[] = []) => { }; export const ModalInspectQuery = ({ + adHocDataViews, additionalRequests, additionalResponses, closeModal, @@ -120,6 +122,7 @@ export const ModalInspectQuery = ({ const { selectedPatterns } = useSourcererDataView( inputId === 'timeline' ? SourcererScopeName.timeline : getScopeFromPath(pathname) ); + const requests: string[] = [request, ...(additionalRequests != null ? additionalRequests : [])]; const responses: string[] = [ response, @@ -150,7 +153,13 @@ export const ModalInspectQuery = ({ ), description: ( -

{formatIndexPatternRequested(inspectRequests[0]?.index ?? [])}

+

+ {formatIndexPatternRequested( + adHocDataViews != null && adHocDataViews.length > 0 + ? adHocDataViews + : inspectRequests[0]?.index ?? [] + )} +

{!isSourcererPattern && (

diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx index 61efd3c9065430..1b136c1b0c4640 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx @@ -15,7 +15,6 @@ import { MatrixHistogramType } from '../../../../common/search_strategy/security import { TestProviders } from '../../mock'; import { mockRuntimeMappings } from '../../containers/source/mock'; import { dnsTopDomainsLensAttributes } from '../visualization_actions/lens_attributes/network/dns_top_domains'; -import { useRouteSpy } from '../../utils/route/use_route_spy'; import { useQueryToggle } from '../../containers/query_toggle'; jest.mock('../../containers/query_toggle'); @@ -176,15 +175,7 @@ describe('Matrix Histogram Component', () => { }); describe('Inspect button', () => { - test("it doesn't render Inspect button by default on Host page", () => { - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: 'mockHost', - pageName: 'hosts', - tabName: 'events', - }, - ]); - + test("it doesn't render Inspect button by default", () => { const testProps = { ...mockMatrixOverTimeHistogramProps, lensAttributes: dnsTopDomainsLensAttributes, @@ -194,56 +185,10 @@ describe('Matrix Histogram Component', () => { }); expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(false); }); - - test("it doesn't render Inspect button by default on Network page", () => { - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: undefined, - pageName: 'network', - tabName: 'external-alerts', - }, - ]); - - const testProps = { - ...mockMatrixOverTimeHistogramProps, - lensAttributes: dnsTopDomainsLensAttributes, - }; - wrapper = mount(, { - wrappingComponent: TestProviders, - }); - expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(false); - }); - - test('it render Inspect button by default on other pages', () => { - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: undefined, - pageName: 'overview', - tabName: undefined, - }, - ]); - - const testProps = { - ...mockMatrixOverTimeHistogramProps, - lensAttributes: dnsTopDomainsLensAttributes, - }; - wrapper = mount(, { - wrappingComponent: TestProviders, - }); - expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(true); - }); }); describe('VisualizationActions', () => { - test('it renders VisualizationActions on Host page if lensAttributes is provided', () => { - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: 'mockHost', - pageName: 'hosts', - tabName: 'events', - }, - ]); - + test('it renders VisualizationActions if lensAttributes is provided', () => { const testProps = { ...mockMatrixOverTimeHistogramProps, lensAttributes: dnsTopDomainsLensAttributes, @@ -256,48 +201,6 @@ describe('Matrix Histogram Component', () => { 'histogram-viz-actions' ); }); - - test('it renders VisualizationActions on Network page if lensAttributes is provided', () => { - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: undefined, - pageName: 'network', - tabName: 'events', - }, - ]); - - const testProps = { - ...mockMatrixOverTimeHistogramProps, - lensAttributes: dnsTopDomainsLensAttributes, - }; - wrapper = mount(, { - wrappingComponent: TestProviders, - }); - expect(wrapper.find('[data-test-subj="mock-viz-actions"]').exists()).toBe(true); - expect(wrapper.find('[data-test-subj="mock-viz-actions"]').prop('className')).toEqual( - 'histogram-viz-actions' - ); - }); - - test("it doesn't renders VisualizationActions except Host / Network pages", () => { - const testProps = { - ...mockMatrixOverTimeHistogramProps, - lensAttributes: dnsTopDomainsLensAttributes, - }; - - (useRouteSpy as jest.Mock).mockReturnValue([ - { - detailName: undefined, - pageName: 'overview', - tabName: undefined, - }, - ]); - - wrapper = mount(, { - wrappingComponent: TestProviders, - }); - expect(wrapper.find('[data-test-subj="mock-viz-actions"]').exists()).toBe(false); - }); }); describe('toggle query', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index 82a37d20b1cfee..724da1e1e512e0 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -31,11 +31,13 @@ import type { GlobalTimeArgs } from '../../containers/use_global_time'; import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { InputsModelId } from '../../store/inputs/constants'; import { HoverVisibilityContainer } from '../hover_visibility_container'; -import { HISTOGRAM_ACTIONS_BUTTON_CLASS, VisualizationActions } from '../visualization_actions'; +import { VisualizationActions } from '../visualization_actions'; import type { GetLensAttributes, LensAttributes } from '../visualization_actions/types'; -import { SecurityPageName } from '../../../../common/constants'; -import { useRouteSpy } from '../../utils/route/use_route_spy'; import { useQueryToggle } from '../../containers/query_toggle'; +import { LensEmbeddable } from '../visualization_actions/lens_embeddable'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; +import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; +import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../visualization_actions/utils'; export type MatrixHistogramComponentProps = MatrixHistogramProps & Omit & { @@ -69,6 +71,8 @@ const HistogramPanel = styled(Panel)<{ height?: number }>` ${({ height }) => (height != null ? `min-height: ${height}px;` : '')} `; +const ChartHeight = '150px'; + export const MatrixHistogramComponent: React.FC = ({ chartHeight, defaultStackByOption, @@ -164,6 +168,13 @@ export const MatrixHistogramComponent: React.FC = [setQuerySkip, setToggleStatus] ); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: id, + skip: !isChartEmbeddablesEnabled, + }); + const matrixHistogramRequest = { endDate, errorMessage, @@ -175,16 +186,10 @@ export const MatrixHistogramComponent: React.FC = stackByField: selectedStackByOption.value, runtimeMappings, isPtrIncluded, - skip: querySkip, + skip: querySkip || isChartEmbeddablesEnabled, }; const [loading, { data, inspect, totalCount, refetch }] = useMatrixHistogramCombined(matrixHistogramRequest); - const [{ pageName }] = useRouteSpy(); - - const onHostOrNetworkOrUserPage = - pageName === SecurityPageName.hosts || - pageName === SecurityPageName.network || - pageName === SecurityPageName.users; const titleWithStackByField = useMemo( () => (title != null && typeof title === 'function' ? title(selectedStackByOption) : title), @@ -209,22 +214,31 @@ export const MatrixHistogramComponent: React.FC = useEffect(() => { if (!loading && !isInitialLoading) { - setQuery({ id, inspect, loading, refetch }); + setQuery({ + id, + inspect, + loading, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + searchSessionId, + }); } if (isInitialLoading && !!barChartData && data) { setIsInitialLoading(false); } }, [ - setQuery, + barChartData, + data, id, inspect, + isChartEmbeddablesEnabled, + isInitialLoading, loading, refetch, - isInitialLoading, - barChartData, - data, + refetchByRestartingSession, + searchSessionId, setIsInitialLoading, + setQuery, ]); const timerange = useMemo(() => ({ from: startDate, to: endDate }), [startDate, endDate]); @@ -236,7 +250,7 @@ export const MatrixHistogramComponent: React.FC = <> = toggleQuery={toggleQuery} subtitle={subtitleWithCounts} inspectMultiple - showInspectButton={showInspectButton || !onHostOrNetworkOrUserPage} + showInspectButton={showInspectButton && !isChartEmbeddablesEnabled} isInspectDisabled={filterQuery === undefined} > - {onHostOrNetworkOrUserPage && (getLensAttributes || lensAttributes) && timerange && ( + {(getLensAttributes || lensAttributes) && timerange && ( = {toggleStatus ? ( - isInitialLoading ? ( + isChartEmbeddablesEnabled && (getLensAttributes || lensAttributes) && timerange ? ( + + ) : isInitialLoading ? ( ) : ( { children: React.ReactNode; }, { - searchSessionId: string; + searchSessionId: string | undefined; refetchByRestartingSession: Refetch; } >; diff --git a/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx b/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx index d1bd6020fb1316..23c95449f28eed 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { useCallback, useRef, useEffect, useMemo } from 'react'; +import { useCallback, useRef, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { useKibana } from '../../lib/kibana'; @@ -23,21 +23,28 @@ interface UseRefetchByRestartingSessionProps { export const useRefetchByRestartingSession = ({ inputId, queryId, + skip, }: UseRefetchByRestartingSessionProps): { - searchSessionId: string; + searchSessionId: string | undefined; refetchByRestartingSession: Refetch; } => { const dispatch = useDispatch(); const { data } = useKibana().services; const session = useRef(data.search.session); - const searchSessionId = useMemo(() => session.current.start(), [session]); + const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); const getTimelineQuery = inputsSelectors.timelineQueryByIdSelector(); - const { selectedInspectIndex } = useDeepEqualSelector((state) => - inputId === InputsModelId.global - ? getGlobalQuery(state, queryId) - : getTimelineQuery(state, queryId) + const { selectedInspectIndex, searchSessionId: existingSearchSessionId } = useDeepEqualSelector( + (state) => + inputId === InputsModelId.global + ? getGlobalQuery(state, queryId) + : getTimelineQuery(state, queryId) + ); + + const searchSessionId = useMemo( + () => (skip ? undefined : existingSearchSessionId ?? session.current.start()), + [existingSearchSessionId, skip] ); const refetchByRestartingSession = useCallback(() => { @@ -51,16 +58,10 @@ export const useRefetchByRestartingSession = ({ * like most of our components, it refetches when receiving a new search * session ID. **/ - searchSessionId: session.current.start(), + searchSessionId: skip ? undefined : session.current.start(), }) ); - }, [dispatch, queryId, selectedInspectIndex]); - - useEffect(() => { - return () => { - data.search.session.clear(); - }; - }); + }, [dispatch, queryId, selectedInspectIndex, skip]); return { searchSessionId, refetchByRestartingSession }; }; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/index.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/index.tsx new file mode 100644 index 00000000000000..b222b3a2a8be7a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/index.tsx @@ -0,0 +1,9 @@ +/* + * 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 from 'react'; + +export const VisualizationActions = () =>

; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/lens_embeddable.tsx new file mode 100644 index 00000000000000..051ff99a19361e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/lens_embeddable.tsx @@ -0,0 +1,9 @@ +/* + * 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 from 'react'; + +export const LensEmbeddable = () =>
; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx index 6e5814de8172ea..16336bbded8260 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx @@ -24,6 +24,7 @@ import { MORE_ACTIONS, OPEN_IN_LENS, } from './translations'; +import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from './utils'; const Wrapper = styled.div` &.viz-actions { @@ -37,8 +38,6 @@ const Wrapper = styled.div` } `; -export const HISTOGRAM_ACTIONS_BUTTON_CLASS = 'histogram-actions-trigger'; - const VisualizationActionsComponent: React.FC = ({ className, getLensAttributes, @@ -195,7 +194,7 @@ const VisualizationActionsComponent: React.FC = ({ () => ( + ({ + title: 'Alerts', + description: '', + visualizationType: 'lnsPie', + state: { + visualization: { + shape: 'donut', + layers: [ + { + layerId: '51ed355e-6e23-4038-a417-f653a1160370', + primaryGroups: ['a9b43606-7ff7-46ae-a47c-85bed80fab9a'], + metrics: ['21cc4a49-3780-4b1a-be28-f02fa5303d24'], + numberDisplay: 'value', + categoryDisplay: 'hide', + legendDisplay: 'hide', + nestedLegend: true, + layerType: 'data', + emptySizeRatio: 0.85, + percentDecimals: 2, + }, + ], + }, + query: { + query: '', + language: 'kuery', + }, + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + index: 'a1aaa83b-5026-444e-9465-50e0afade01c', + key: stackByField, + field: stackByField, + params: { + query: extraOptions?.status, + }, + type: 'phrase', + }, + query: { + match_phrase: { + [stackByField]: extraOptions?.status, + }, + }, + }, + ], + datasourceStates: { + formBased: { + layers: { + '51ed355e-6e23-4038-a417-f653a1160370': { + columns: { + 'a9b43606-7ff7-46ae-a47c-85bed80fab9a': { + label: 'Filters', + dataType: 'string', + operationType: 'filters', + scale: 'ordinal', + isBucketed: true, + params: { + filters: [ + { + input: { + query: 'kibana.alert.severity: "critical"', + language: 'kuery', + }, + label: 'Critical', + }, + { + label: 'High', + input: { + query: 'kibana.alert.severity : "high" ', + language: 'kuery', + }, + }, + { + input: { + query: 'kibana.alert.severity: "medium"', + language: 'kuery', + }, + label: 'Medium', + }, + { + input: { + query: 'kibana.alert.severity : "low" ', + language: 'kuery', + }, + label: 'Low', + }, + ], + }, + }, + '21cc4a49-3780-4b1a-be28-f02fa5303d24': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + filter: { + query: '', + language: 'kuery', + }, + params: { + emptyAsNull: true, + }, + }, + }, + columnOrder: [ + 'a9b43606-7ff7-46ae-a47c-85bed80fab9a', + '21cc4a49-3780-4b1a-be28-f02fa5303d24', + ], + sampling: 1, + incompleteColumns: {}, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [], + adHocDataViews: {}, + }, + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: 'indexpattern-datasource-layer-51ed355e-6e23-4038-a417-f653a1160370', + }, + { + type: 'index-pattern', + name: 'a1aaa83b-5026-444e-9465-50e0afade01c', + id: '{dataViewId}', + }, + ], + } as LensAttributes); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts new file mode 100644 index 00000000000000..b3ccb0b0f4b733 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts @@ -0,0 +1,84 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../../mocks'; + +import { useLensAttributes } from '../../../use_lens_attributes'; + +import { getAlertsHistogramLensAttributes } from './alerts_histogram'; + +jest.mock('../../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + dataViewId: 'security-solution-my-test', + indicesExist: true, + selectedPatterns: ['signal-index'], + }), +})); + +jest.mock('../../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'mockRule', + pageName: 'rules', + tabName: 'alerts', + }, + ]), +})); + +describe('getAlertsHistogramLensAttributes', () => { + it('should render without extra options', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getAlertsHistogramLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - filters', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { + filters: [ + { + meta: { + type: 'phrases', + key: '_index', + params: ['.alerts-security.alerts-default'], + alias: null, + negate: false, + disabled: false, + }, + query: { + bool: { + should: [ + { + match_phrase: { + _index: '.alerts-security.alerts-default', + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + ], + }, + getLensAttributes: getAlertsHistogramLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts new file mode 100644 index 00000000000000..557bfd50fce72d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts @@ -0,0 +1,125 @@ +/* + * 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 type { GetLensAttributes, LensAttributes } from '../../../types'; + +export const getAlertsHistogramLensAttributes: GetLensAttributes = ( + stackByField = 'kibana.alert.rule.name', + extraOptions +) => + ({ + title: 'Alerts', + description: '', + visualizationType: 'lnsXY', + state: { + visualization: { + title: 'Empty XY chart', + legend: { + isVisible: true, + position: 'right', + isInside: true, + }, + valueLabels: 'hide', + preferredSeriesType: 'bar_stacked', + layers: [ + { + layerId: '0039eb0c-9a1a-4687-ae54-0f4e239bec75', + accessors: ['e09e0380-0740-4105-becc-0a4ca12e3944'], + position: 'top', + seriesType: 'bar_stacked', + showGridlines: false, + layerType: 'data', + xAccessor: 'aac9d7d0-13a3-480a-892b-08207a787926', + splitAccessor: '34919782-4546-43a5-b668-06ac934d3acd', + }, + ], + yRightExtent: { + mode: 'full', + }, + yLeftExtent: { + mode: 'full', + }, + axisTitlesVisibilitySettings: { + x: false, + yLeft: false, + yRight: true, + }, + valuesInLegend: true, + }, + query: { + query: '', + language: 'kuery', + }, + filters: extraOptions?.filters ? extraOptions.filters : [], + datasourceStates: { + formBased: { + layers: { + '0039eb0c-9a1a-4687-ae54-0f4e239bec75': { + columns: { + 'aac9d7d0-13a3-480a-892b-08207a787926': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { + interval: 'auto', + }, + }, + 'e09e0380-0740-4105-becc-0a4ca12e3944': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + }, + '34919782-4546-43a5-b668-06ac934d3acd': { + label: `Top values of ${stackByField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: stackByField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'e09e0380-0740-4105-becc-0a4ca12e3944', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + secondaryFields: [], + }, + }, + }, + columnOrder: [ + '34919782-4546-43a5-b668-06ac934d3acd', + 'aac9d7d0-13a3-480a-892b-08207a787926', + 'e09e0380-0740-4105-becc-0a4ca12e3944', + ], + incompleteColumns: {}, + }, + }, + }, + }, + internalReferences: [], + adHocDataViews: {}, + }, + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: 'indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75', + }, + ], + } as LensAttributes); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts new file mode 100644 index 00000000000000..34f9cb45eab403 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts @@ -0,0 +1,96 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../../mocks'; + +import { useLensAttributes } from '../../../use_lens_attributes'; + +import { getAlertsTableLensAttributes } from './alerts_table'; + +jest.mock('../../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + dataViewId: 'security-solution-my-test', + indicesExist: true, + selectedPatterns: ['signal-index'], + }), +})); + +jest.mock('../../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + pageName: 'alerts', + }, + ]), +})); + +describe('getAlertsTableLensAttributes', () => { + it('should render without extra options', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getAlertsTableLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - filters', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { + filters: [ + { + meta: { + type: 'phrases', + key: '_index', + params: ['.alerts-security.alerts-default'], + alias: null, + negate: false, + disabled: false, + }, + query: { + bool: { + should: [ + { + match_phrase: { + _index: '.alerts-security.alerts-default', + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + ], + }, + getLensAttributes: getAlertsTableLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - breakdownField', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { breakdownField: 'agent.type' }, + getLensAttributes: getAlertsTableLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts new file mode 100644 index 00000000000000..cea14505f2ebcb --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts @@ -0,0 +1,136 @@ +/* + * 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 type { GetLensAttributes } from '../../../types'; + +export const getAlertsTableLensAttributes: GetLensAttributes = ( + stackByField = 'kibana.alert.rule.name', + extraOptions +) => ({ + title: 'Alerts', + description: '', + visualizationType: 'lnsDatatable', + state: { + visualization: { + columns: [ + { + columnId: '2881fedd-54b7-42ba-8c97-5175dec86166', + isTransposed: false, + width: 362, + }, + { + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + isTransposed: false, + }, + { + columnId: '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + isTransposed: false, + }, + ], + layerId: '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', + layerType: 'data', + paging: { + size: 10, + enabled: true, + }, + }, + query: { + query: '', + language: 'kuery', + }, + filters: extraOptions?.filters ? extraOptions.filters : [], + datasourceStates: { + formBased: { + layers: { + '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b': { + columns: { + '2881fedd-54b7-42ba-8c97-5175dec86166': { + label: `Top values of ${stackByField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: stackByField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, + 'f04a71a3-399f-4d32-9efc-8a005e989991': { + label: `Count of ${extraOptions?.breakdownField}`, + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: extraOptions?.breakdownField, + params: { + emptyAsNull: true, + }, + }, + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { + label: `Top values of ${extraOptions?.breakdownField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: extraOptions?.breakdownField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, + }, + columnOrder: [ + '2881fedd-54b7-42ba-8c97-5175dec86166', + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + 'f04a71a3-399f-4d32-9efc-8a005e989991', + ], + sampling: 1, + incompleteColumns: {}, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [], + adHocDataViews: {}, + }, + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: 'indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', + }, + ], +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts new file mode 100644 index 00000000000000..af80ae5a3f5b69 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts @@ -0,0 +1,96 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../../mocks'; + +import { useLensAttributes } from '../../../use_lens_attributes'; + +import { getAlertsTreemapLensAttributes } from './alerts_treemap'; + +jest.mock('../../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + dataViewId: 'security-solution-my-test', + indicesExist: true, + selectedPatterns: ['signal-index'], + }), +})); + +jest.mock('../../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + pageName: 'alerts', + }, + ]), +})); + +describe('getAlertsTreemapLensAttributes', () => { + it('should render without extra options', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getAlertsTreemapLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - filters', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { + filters: [ + { + meta: { + type: 'phrases', + key: '_index', + params: ['.alerts-security.alerts-default'], + alias: null, + negate: false, + disabled: false, + }, + query: { + bool: { + should: [ + { + match_phrase: { + _index: '.alerts-security.alerts-default', + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + ], + }, + getLensAttributes: getAlertsTreemapLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - breakdownField', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { breakdownField: 'agent.type' }, + getLensAttributes: getAlertsTreemapLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts new file mode 100644 index 00000000000000..719574b6aa724b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts @@ -0,0 +1,133 @@ +/* + * 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 type { GetLensAttributes } from '../../../types'; + +export const getAlertsTreemapLensAttributes: GetLensAttributes = ( + stackByField = 'kibana.alert.rule.name', + extraOptions +) => ({ + title: 'Alerts', + description: '', + visualizationType: 'lnsPie', + state: { + visualization: { + shape: 'treemap', + layers: [ + { + layerId: '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', + primaryGroups: [ + '2881fedd-54b7-42ba-8c97-5175dec86166', + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + ], + metrics: ['f04a71a3-399f-4d32-9efc-8a005e989991'], + numberDisplay: 'value', + categoryDisplay: 'default', + legendDisplay: 'show', + nestedLegend: true, + layerType: 'data', + legendSize: 'xlarge', + legendPosition: 'left', + }, + ], + }, + query: { + query: '', + language: 'kuery', + }, + filters: extraOptions?.filters ? extraOptions.filters : [], + datasourceStates: { + formBased: { + layers: { + '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b': { + columns: { + '2881fedd-54b7-42ba-8c97-5175dec86166': { + label: `Top values of ${stackByField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: stackByField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { + label: `Top values of ${extraOptions?.breakdownField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: extraOptions?.breakdownField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, + 'f04a71a3-399f-4d32-9efc-8a005e989991': { + label: 'Maximum of kibana.alert.risk_score', + dataType: 'number', + operationType: 'max', + sourceField: 'kibana.alert.risk_score', + isBucketed: false, + scale: 'ratio', + params: { + emptyAsNull: true, + }, + }, + }, + columnOrder: [ + '2881fedd-54b7-42ba-8c97-5175dec86166', + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + 'f04a71a3-399f-4d32-9efc-8a005e989991', + ], + sampling: 1, + incompleteColumns: {}, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [], + adHocDataViews: {}, + }, + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: 'indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', + }, + ], +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts new file mode 100644 index 00000000000000..fc7071cc8afffd --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts @@ -0,0 +1,162 @@ +/* + * 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 type { GetLensAttributes } from '../../../types'; + +export const getRulePreviewLensAttributes: GetLensAttributes = ( + stackByField = 'event.category', + extraOptions +) => ({ + title: 'alert preview', + description: '', + visualizationType: 'lnsXY', + state: { + visualization: { + title: 'Empty XY chart', + legend: { + isVisible: false, + position: 'left', + }, + valueLabels: 'hide', + preferredSeriesType: 'bar_stacked', + layers: [ + { + layerId: '1469e8f5-c685-4f37-96f4-2084c0537bf7', + accessors: ['9c89324b-0c59-4403-9698-d989a09dc5a8'], + position: 'top', + seriesType: 'bar_stacked', + showGridlines: false, + layerType: 'data', + xAccessor: 'eba07b4d-766d-49d7-8435-d40367d3d055', + splitAccessor: 'e92c8920-0449-4564-81f4-8945517817a4', + }, + ], + valuesInLegend: true, + yTitle: '', + axisTitlesVisibilitySettings: { + x: false, + yLeft: false, + yRight: true, + }, + }, + query: { + query: '', + language: 'kuery', + }, + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + index: '1f76cd3c-7267-424d-9bbc-213c8c3def61', + key: 'kibana.alert.rule.uuid', + field: 'kibana.alert.rule.uuid', + params: { + query: extraOptions?.ruleId, + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'kibana.alert.rule.uuid': extraOptions?.ruleId, + }, + }, + }, + ], + datasourceStates: { + formBased: { + layers: { + '1469e8f5-c685-4f37-96f4-2084c0537bf7': { + columns: { + '9c89324b-0c59-4403-9698-d989a09dc5a8': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + params: { + emptyAsNull: true, + }, + }, + 'eba07b4d-766d-49d7-8435-d40367d3d055': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { + interval: 'auto', + includeEmptyRows: true, + dropPartials: false, + }, + }, + 'e92c8920-0449-4564-81f4-8945517817a4': { + label: `Top 10 values of ${stackByField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: stackByField, + isBucketed: true, + params: { + size: 10, + orderBy: { + type: 'column', + columnId: '9c89324b-0c59-4403-9698-d989a09dc5a8', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, + }, + columnOrder: [ + 'e92c8920-0449-4564-81f4-8945517817a4', + 'eba07b4d-766d-49d7-8435-d40367d3d055', + '9c89324b-0c59-4403-9698-d989a09dc5a8', + ], + sampling: 1, + incompleteColumns: {}, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [ + { + type: 'index-pattern', + id: '1f76cd3c-7267-424d-9bbc-213c8c3def61', + name: 'indexpattern-datasource-layer-1469e8f5-c685-4f37-96f4-2084c0537bf7', + }, + ], + adHocDataViews: { + '1f76cd3c-7267-424d-9bbc-213c8c3def61': { + id: '1f76cd3c-7267-424d-9bbc-213c8c3def61', + title: `.preview.alerts-security.alerts-${extraOptions?.spaceId}`, + timeFieldName: '@timestamp', + sourceFilters: [], + fieldFormats: {}, + runtimeFieldMap: {}, + fieldAttrs: {}, + allowNoIndex: false, + name: `.preview.alerts-security.alerts-${extraOptions?.spaceId}`, + }, + }, + }, + references: [], +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts index 808789db397e9c..4a5e871bcf07c0 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/event.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts similarity index 98% rename from x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/event.test.ts rename to x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts index 107b63716f4047..87d246fc2350b6 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/event.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/events.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts similarity index 89% rename from x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/events.ts rename to x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts index f07716f8c4bd7b..e48f6aa6c1a87f 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/events.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts @@ -11,7 +11,7 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( stackByField = 'event.action' ) => ({ - title: 'Host - events', + title: 'Events', description: '', visualizationType: 'lnsXY', state: { @@ -77,11 +77,11 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( sourceField: '___records___', }, '34919782-4546-43a5-b668-06ac934d3acd': { - label: `Top values of ${stackByField}`, // could be event.dataset or event.module + label: `Top values of ${stackByField}`, dataType: 'string', operationType: 'terms', scale: 'ordinal', - sourceField: `${stackByField}`, // could be event.dataset or event.module + sourceField: `${stackByField}`, isBucketed: true, params: { size: 10, @@ -89,7 +89,7 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( type: 'column', columnId: 'e09e0380-0740-4105-becc-0a4ca12e3944', }, - orderDirection: 'asc', + orderDirection: 'desc', otherBucket: true, missingBucket: false, parentFormat: { @@ -110,11 +110,6 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( }, }, references: [ - { - type: 'index-pattern', - id: '{dataViewId}', - name: 'indexpattern-datasource-current-indexpattern', - }, { type: 'index-pattern', id: '{dataViewId}', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts index da890c4d49fe05..575960076dda4f 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts index 52484448729428..5f40d582d08f80 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_area.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts index 06a884c15e4d63..04049016befb7a 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_host_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts index 63f50b141b5b0c..d7608d90e2a5cb 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts index f04f0de2b8be71..fb9d56bfd77aac 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts index 9dd67a2d5ab400..af0a079f4592d3 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_destination_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts index 6e69495e63a0eb..07a9bed7e90f96 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_source_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts index a726a44e34e394..87237cf2675dad 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts index b19b5c1f2f1bac..33ea4dd0027b02 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts index 4ca26f222021ab..a335057621a0ac 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts index f06f478ca0e2be..c00e82fadf3bcf 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts index 64b9be02a1d18a..54cf848b95881e 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts index 8bb98ddaf95cfe..85fc74ca516320 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts index 894144d383b58d..557965cccaae40 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts index 7d65e042554b35..3b7eede95e3edc 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_destination_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts index 88042d86632508..265d1d00bf1875 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_source_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts index 43cb5be3a97355..e4a0ecf87aa822 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_area.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts index 0a5ec9891d26f9..b35b1d6b43b15b 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_total_users_metric.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts index e251ccf51c5e51..25ef5b1eabfcd9 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts index b51d3e474a7053..87a1270ec52315 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts index 41590d330cd450..1c02989f94d5a5 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts index 570e03325ec341..be232d12247276 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.test.ts @@ -16,6 +16,7 @@ jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], dataViewId: 'security-solution-my-test', + indicesExist: true, }), })); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 7c03e2886a62ba..e37d8807ea4ef2 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -8,8 +8,10 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ViewMode } from '@kbn/embeddable-plugin/public'; import styled from 'styled-components'; +import { EuiEmptyPrompt } from '@elastic/eui'; import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { useKibana } from '../../lib/kibana'; import { useLensAttributes } from './use_lens_attributes'; @@ -20,9 +22,11 @@ import { useDeepEqualSelector } from '../../hooks/use_selector'; import { ModalInspectQuery } from '../inspect/modal'; import { InputsModelId } from '../../store/inputs/constants'; import { getRequestsAndResponses } from './utils'; +import { SourcererScopeName } from '../../store/sourcerer/model'; -const LensComponentWrapper = styled.div<{ height?: string }>` +const LensComponentWrapper = styled.div<{ height?: string; width?: string }>` height: ${({ height }) => height ?? 'auto'}; + width: ${({ width }) => width ?? 'auto'}; > div { background-color: transparent; } @@ -47,14 +51,19 @@ const initVisualizationData: { const style = { height: '100%', minWidth: '100px' }; const LensEmbeddableComponent: React.FC = ({ + extraActions, + extraOptions, getLensAttributes, height: wrapperHeight, id, inputsModelId = InputsModelId.global, + inspectTitle, lensAttributes, + onLoad, + scopeId = SourcererScopeName.default, stackByField, timerange, - inspectTitle, + width: wrapperWidth, }) => { const { lens } = useKibana().services; const dispatch = useDispatch(); @@ -63,8 +72,10 @@ const LensEmbeddableComponent: React.FC = ({ const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); const { searchSessionId } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); const attributes = useLensAttributes({ - lensAttributes, + extraOptions, getLensAttributes, + lensAttributes, + scopeId, stackByField, title: '', }); @@ -81,10 +92,11 @@ const LensEmbeddableComponent: React.FC = ({ ); const actions = useActions({ - withActions: true, attributes, - timeRange: timerange, + extraActions, inspectActionProps, + timeRange: timerange, + withActions: true, }); const handleCloseModal = useCallback(() => { @@ -115,28 +127,69 @@ const LensEmbeddableComponent: React.FC = ({ return { response, additionalResponses }; }, [visualizationData.responses]); - const onLoad = useCallback((isLoading, adapters) => { - if (!adapters) { - return; - } - const data = getRequestsAndResponses(adapters?.requests?.getRequests()); - setVisualizationData({ - requests: data.requests, - responses: data.responses, - isLoading, - }); - }, []); + const callback = useCallback( + (isLoading, adapters) => { + if (!adapters) { + return; + } + const data = getRequestsAndResponses(adapters?.requests?.getRequests()); + + setVisualizationData({ + requests: data.requests, + responses: data.responses, + isLoading, + }); + + if (onLoad != null) { + onLoad({ + requests: data.requests, + responses: data.responses, + isLoading, + }); + } + }, + [onLoad] + ); + + const adHocDataViews = useMemo( + () => + attributes?.state?.adHocDataViews != null + ? Object.values(attributes?.state?.adHocDataViews).reduce((acc, adHocDataView) => { + if (adHocDataView?.name != null) { + acc.push(adHocDataView?.name); + } + return acc; + }, [] as string[]) + : null, + [attributes?.state?.adHocDataViews] + ); + + if ( + !attributes || + (visualizationData?.responses != null && visualizationData?.responses?.length === 0) + ) { + return ( + + } + /> + ); + } return ( <> - {attributes && searchSessionId ? ( - + {attributes && searchSessionId && ( + = ({ showInspector={false} /> - ) : null} - {isShowingModal && requests.request !== null && responses.response !== null && ( + )} + {isShowingModal && requests.request != null && responses.response != null && ( LensAttributes; +export type GetLensAttributes = ( + stackByField?: string, + alertsOptions?: ExtraOptions +) => LensAttributes; export interface VisualizationActionsProps { className?: string; @@ -26,15 +34,29 @@ export interface VisualizationActionsProps { title: React.ReactNode; } +export interface EmbeddableData { + requests: string[]; + responses: string[]; + isLoading: boolean; +} + +export type OnEmbeddableLoaded = (data: EmbeddableData) => void; + export interface LensEmbeddableComponentProps { + adHocDataViews?: string[]; + extraActions?: Action[]; + extraOptions?: ExtraOptions; getLensAttributes?: GetLensAttributes; height?: string; id: string; inputsModelId?: InputsModelId.global | InputsModelId.timeline; inspectTitle?: string; - lensAttributes: LensAttributes; + lensAttributes?: LensAttributes; + onLoad?: OnEmbeddableLoaded; + scopeId?: SourcererScopeName; stackByField?: string; timerange: { from: string; to: string }; + width?: string; } export enum RequestStatus { @@ -74,3 +96,11 @@ export interface Response { json?: { rawResponse?: object }; time?: number; } + +export interface ExtraOptions { + breakdownField?: string; + filters?: Filter[]; + ruleId?: string; + spaceId?: string; + status?: Status; +} diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts index a1761682e9e725..5cd1b01ef7dfb1 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts @@ -22,6 +22,7 @@ export const useActions = ({ attributes, timeRange, inspectActionProps, + extraActions, }: { withActions?: boolean; @@ -30,6 +31,8 @@ export const useActions = ({ timeRange: { from: string; to: string }; inspectActionProps?: { onInspectActionClicked: () => void; isDisabled: boolean }; + + extraActions?: Action[]; }) => { const { lens } = useKibana().services; const { navigateToPrefilledEditor } = lens; @@ -120,7 +123,9 @@ export const useActions = ({ ] ); - return actions; + const withExtraActions = actions.concat(extraActions ?? []); + + return withExtraActions; }; const getOpenInLensAction = ({ callback }: { callback: () => void }): Action => { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx index 76e2f54c62ceac..7a84fc779299c8 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx @@ -12,14 +12,9 @@ import { useLensAttributes } from './use_lens_attributes'; import { hostNameExistsFilter, getHostDetailsPageFilter, getIndexFilters } from './utils'; import { filterFromSearchBar, queryFromSearchBar, wrapper } from './mocks'; +import { useSourcererDataView } from '../../containers/sourcerer'; -jest.mock('../../containers/sourcerer', () => ({ - useSourcererDataView: jest.fn().mockReturnValue({ - selectedPatterns: ['auditbeat-*'], - dataViewId: 'security-solution-default', - }), -})); - +jest.mock('../../containers/sourcerer'); jest.mock('../../utils/route/use_route_spy', () => ({ useRouteSpy: jest.fn().mockReturnValue([ { @@ -31,6 +26,14 @@ jest.mock('../../utils/route/use_route_spy', () => ({ })); describe('useLensAttributes', () => { + beforeEach(() => { + (useSourcererDataView as jest.Mock).mockReturnValue({ + dataViewId: 'security-solution-default', + indicesExist: true, + selectedPatterns: ['auditbeat-*'], + }); + }); + it('should add query', () => { const { result } = renderHook( () => @@ -96,4 +99,22 @@ describe('useLensAttributes', () => { }, ]); }); + + it('should return null if no indices exist', () => { + (useSourcererDataView as jest.Mock).mockReturnValue({ + dataViewId: 'security-solution-default', + indicesExist: false, + selectedPatterns: ['auditbeat-*'], + }); + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getExternalAlertLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current).toBeNull(); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index 635ef09bf31dfd..b50e2f05b0645a 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -12,8 +12,9 @@ import { NetworkRouteType } from '../../../explore/network/pages/navigation/type import { useSourcererDataView } from '../../containers/sourcerer'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { inputsSelectors } from '../../store'; +import { SourcererScopeName } from '../../store/sourcerer/model'; import { useRouteSpy } from '../../utils/route/use_route_spy'; -import type { LensAttributes, GetLensAttributes } from './types'; +import type { LensAttributes, GetLensAttributes, ExtraOptions } from './types'; import { getHostDetailsPageFilter, sourceOrDestinationIpExistsFilter, @@ -22,17 +23,21 @@ import { } from './utils'; export const useLensAttributes = ({ - lensAttributes, + extraOptions, getLensAttributes, + lensAttributes, + scopeId = SourcererScopeName.default, stackByField, title, }: { - lensAttributes?: LensAttributes | null; + extraOptions?: ExtraOptions; getLensAttributes?: GetLensAttributes; + lensAttributes?: LensAttributes | null; + scopeId?: SourcererScopeName; stackByField?: string; title?: string; }): LensAttributes | null => { - const { selectedPatterns, dataViewId } = useSourcererDataView(); + const { selectedPatterns, dataViewId, indicesExist } = useSourcererDataView(scopeId); const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); const getGlobalFiltersQuerySelector = useMemo( () => inputsSelectors.globalFiltersQuerySelector(), @@ -61,16 +66,23 @@ export const useLensAttributes = ({ return []; }, [detailName, pageName]); - const indexFilters = useMemo(() => getIndexFilters(selectedPatterns), [selectedPatterns]); + const attrs: LensAttributes = useMemo( + () => + lensAttributes ?? + ((getLensAttributes && + stackByField && + getLensAttributes(stackByField, extraOptions)) as LensAttributes), + [extraOptions, getLensAttributes, lensAttributes, stackByField] + ); + + const hasAdHocDataViews = Object.values(attrs?.state?.adHocDataViews ?? {}).length > 0; const lensAttrsWithInjectedData = useMemo(() => { if (lensAttributes == null && (getLensAttributes == null || stackByField == null)) { return null; } - const attrs: LensAttributes = - lensAttributes ?? - ((getLensAttributes && stackByField && getLensAttributes(stackByField)) as LensAttributes); + const indexFilters = hasAdHocDataViews ? [] : getIndexFilters(selectedPatterns); return { ...attrs, ...(title != null ? { title } : {}), @@ -85,23 +97,26 @@ export const useLensAttributes = ({ ...indexFilters, ], }, - references: attrs.references.map((ref: { id: string; name: string; type: string }) => ({ + references: attrs?.references?.map((ref: { id: string; name: string; type: string }) => ({ ...ref, id: dataViewId, })), } as LensAttributes; }, [ - lensAttributes, - getLensAttributes, - stackByField, - title, - query, + attrs, + dataViewId, filters, + getLensAttributes, + hasAdHocDataViews, + lensAttributes, pageFilters, + query, + selectedPatterns, + stackByField, tabsFilters, - indexFilters, - dataViewId, + title, ]); - - return lensAttrsWithInjectedData; + return hasAdHocDataViews || (!hasAdHocDataViews && indicesExist) + ? lensAttrsWithInjectedData + : null; }; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts index 446c17c33afff7..6ffdec4913e4fc 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts @@ -8,6 +8,8 @@ import type { Filter } from '@kbn/es-query'; import type { Request } from './types'; +export const VISUALIZATION_ACTIONS_BUTTON_CLASS = 'histogram-actions-trigger'; + export const getHostDetailsPageFilter = (hostName?: string): Filter[] => hostName ? [ @@ -147,8 +149,8 @@ export const getIndexFilters = (selectedPatterns: string[]) => ] : []; -export const getRequestsAndResponses = (requests: Request[]) => { - return requests.reduce( +export const getRequestsAndResponses = (requests: Request[] | null | undefined) => { + return (requests ?? []).reduce( (acc: { requests: string[]; responses: string[] }, req: Request) => { return { requests: [ @@ -168,3 +170,12 @@ export const getRequestsAndResponses = (requests: Request[]) => { { requests: [], responses: [] } ); }; + +export const parseVisualizationData = (data: string[]): T[] => + data.reduce((acc, curr) => { + try { + return [...acc, JSON.parse(curr)]; + } catch (e) { + return acc; + } + }, [] as T[]); diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index 85512855580c31..f75385fdd49553 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -249,7 +249,10 @@ export const useMatrixHistogramCombined = ( const [missingDataLoading, missingDataResponse] = useMatrixHistogram({ ...matrixHistogramQueryProps, includeMissingData: false, - skip: skipMissingData || matrixHistogramQueryProps.filterQuery === undefined, + skip: + skipMissingData || + matrixHistogramQueryProps.filterQuery === undefined || + matrixHistogramQueryProps.skip, }); const combinedLoading = useMemo( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index f9638456e68144..2648804a934933 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -827,12 +827,12 @@ const RuleDetailsPageComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx index fc752b243f9b1c..ee4f6810ffac07 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx @@ -10,12 +10,17 @@ import { waitFor, act } from '@testing-library/react'; import { mount } from 'enzyme'; import { AlertsCountPanel } from '.'; + +import type { Status } from '../../../../../common/detection_engine/schemas/common'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { DEFAULT_STACK_BY_FIELD, DEFAULT_STACK_BY_FIELD1 } from '../common/config'; import { TestProviders } from '../../../../common/mock'; import { ChartContextMenu } from '../../../pages/detection_engine/chart_panels/chart_context_menu'; import { TABLE } from '../../../pages/detection_engine/chart_panels/chart_select/translations'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { useInspectButton } from '../common/hooks'; const from = '2022-07-28T08:20:18.966Z'; const to = '2022-07-28T08:20:18.966Z'; @@ -50,18 +55,35 @@ jest.mock('../../../containers/detection_engine/alerts/use_query', () => { }; }); -describe('AlertsCountPanel', () => { - const defaultProps = { - inspectTitle: TABLE, - signalIndexName: 'signalIndexName', - stackByField0: DEFAULT_STACK_BY_FIELD, - stackByField1: DEFAULT_STACK_BY_FIELD1, - setStackByField0: jest.fn(), - setStackByField1: jest.fn(), - }; - const mockSetToggle = jest.fn(); - const mockUseQueryToggle = useQueryToggle as jest.Mock; +jest.mock('../../../../common/hooks/use_experimental_features'); +jest.mock('../../../../common/components/page/use_refetch_by_session'); +jest.mock('../../../../common/components/visualization_actions/lens_embeddable'); +jest.mock('../../../../common/components/page/use_refetch_by_session', () => ({ + useRefetchByRestartingSession: jest.fn().mockReturnValue({ + searchSessionId: 'mockSearchSessionId', + refetchByRestartingSession: jest.fn(), + }), +})); +jest.mock('../common/hooks', () => ({ + useInspectButton: jest.fn(), + useStackByFields: jest.fn(), +})); +const defaultProps = { + inspectTitle: TABLE, + setStackByField0: jest.fn(), + setStackByField1: jest.fn(), + showBuildingBlockAlerts: false, + showOnlyThreatIndicatorAlerts: false, + signalIndexName: 'signalIndexName', + stackByField0: DEFAULT_STACK_BY_FIELD, + stackByField1: DEFAULT_STACK_BY_FIELD1, + status: 'open' as Status, +}; +const mockUseQueryToggle = useQueryToggle as jest.Mock; +const mockSetToggle = jest.fn(); + +describe('AlertsCountPanel', () => { beforeEach(() => { jest.clearAllMocks(); mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); @@ -197,3 +219,59 @@ describe('AlertsCountPanel', () => { }); }); }); + +describe('when isChartEmbeddablesEnabled = true', () => { + const mockSearchSessionId = 'mockSearchSessionId'; + const mockRefetchByRestartingSession = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + + mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); + + (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ + searchSessionId: mockSearchSessionId, + refetchByRestartingSession: mockRefetchByRestartingSession, + }); + + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + }); + + it('refetch data by refetchByRestartingSession', async () => { + await act(async () => { + mount( + + + + ); + expect((useInspectButton as jest.Mock).mock.calls[0][0].refetch).toEqual( + mockRefetchByRestartingSession + ); + expect((useInspectButton as jest.Mock).mock.calls[0][0].searchSessionId).toEqual( + mockSearchSessionId + ); + }); + }); + + it('renders LensEmbeddable', async () => { + await act(async () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="lens-embeddable"]').exists()).toBeTruthy(); + }); + }); + + it('should skip calling getAlertsRiskQuery', async () => { + await act(async () => { + mount( + + + + ); + expect(mockUseQueryAlerts.mock.calls[0][0].skip).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index 8fceaece8ed2bf..ffdf68642bd24e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -6,6 +6,7 @@ */ import type { EuiComboBox } from '@elastic/eui'; +import type { Action } from '@kbn/ui-actions-plugin/public'; import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; import React, { memo, useMemo, useState, useEffect, useCallback } from 'react'; import uuid from 'uuid'; @@ -27,16 +28,24 @@ import { KpiPanel } from '../common/components'; import { useInspectButton } from '../common/hooks'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { FieldSelection } from '../../../../common/components/field_selection'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; +import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; +import { getAlertsTableLensAttributes as getLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_table'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; export const DETECTIONS_ALERTS_COUNT_ID = 'detections-alerts-count'; interface AlertsCountPanelProps { alignHeader?: 'center' | 'baseline' | 'stretch' | 'flexStart' | 'flexEnd'; chartOptionsContextMenu?: (queryId: string) => React.ReactNode; + extraActions?: Action[]; filters?: Filter[]; inspectTitle: string; panelHeight?: number; query?: Query; + runtimeMappings?: MappingRuntimeFields; setStackByField0: (stackBy: string) => void; setStackByField0ComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; setStackByField1: (stackBy: string | undefined) => void; @@ -48,13 +57,14 @@ interface AlertsCountPanelProps { stackByField1ComboboxRef?: React.RefObject>; stackByWidth?: number; title?: React.ReactNode; - runtimeMappings?: MappingRuntimeFields; } +const ChartHeight = '180px'; export const AlertsCountPanel = memo( ({ alignHeader, chartOptionsContextMenu, + extraActions, filters, inspectTitle, panelHeight, @@ -100,14 +110,27 @@ export const AlertsCountPanel = memo( setQuerySkip(!toggleStatus); }, [toggleStatus]); const toggleQuery = useCallback( - (status: boolean) => { - setToggleStatus(status); + (newToggleStatus: boolean) => { + setToggleStatus(newToggleStatus); // toggle on = skipQuery false - setQuerySkip(!status); + setQuerySkip(!newToggleStatus); }, [setQuerySkip, setToggleStatus] ); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const timerange = useMemo(() => ({ from, to }), [from, to]); + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: uniqueQueryId, + }); + const extraVisualizationOptions = useMemo( + () => ({ + breakdownField: stackByField1, + filters, + }), + [filters, stackByField1] + ); const { loading: isLoadingAlerts, data: alertsData, @@ -125,7 +148,7 @@ export const AlertsCountPanel = memo( runtimeMappings, }), indexName: signalIndexName, - skip: querySkip, + skip: querySkip || isChartEmbeddablesEnabled, queryName: ALERTS_QUERY_NAMES.COUNT, }); @@ -151,13 +174,14 @@ export const AlertsCountPanel = memo( ]); useInspectButton({ - setQuery, - response, - request, - refetch, - uniqueQueryId, deleteQuery, loading: isLoadingAlerts, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + request, + response, + searchSessionId, + setQuery, + uniqueQueryId, }); return ( @@ -181,7 +205,9 @@ export const AlertsCountPanel = memo( toggleQuery={toggleQuery} > ( uniqueQueryId={uniqueQueryId} /> - {toggleStatus && alertsData != null && ( - - )} + {toggleStatus ? ( + isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( + + ) : alertsData != null ? ( + + ) : null + ) : null} ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx index c47353aa04b06f..ccbf87b4e9bfbc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx @@ -21,6 +21,9 @@ import * as helpers from './helpers'; import { mockAlertSearchResponse } from './mock_data'; import { ChartContextMenu } from '../../../pages/detection_engine/chart_panels/chart_context_menu'; import { AlertsHistogramPanel, LEGEND_WITH_COUNTS_WIDTH } from '.'; +import { useInspectButton } from '../common/hooks'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; jest.mock('../../../../common/containers/query_toggle'); @@ -95,11 +98,30 @@ jest.mock('../../../containers/detection_engine/alerts/use_query', () => { useQueryAlerts: (...props: unknown[]) => mockUseQueryAlerts(...props), }; }); +jest.mock('../../../../common/hooks/use_experimental_features'); +jest.mock('../../../../common/components/page/use_refetch_by_session'); +jest.mock('../../../../common/components/visualization_actions/lens_embeddable'); + +jest.mock('../../../../common/components/page/use_refetch_by_session', () => ({ + useRefetchByRestartingSession: jest.fn().mockReturnValue({ + searchSessionId: 'mockSearchSessionId', + refetchByRestartingSession: jest.fn(), + }), +})); +jest.mock('../common/hooks', () => { + const actual = jest.requireActual('../common/hooks'); + return { + ...actual, + useInspectButton: jest.fn(), + }; +}); describe('AlertsHistogramPanel', () => { const defaultProps = { - signalIndexName: 'signalIndexName', setQuery: jest.fn(), + showBuildingBlockAlerts: false, + showOnlyThreatIndicatorAlerts: false, + signalIndexName: 'signalIndexName', updateDateRange: jest.fn(), }; @@ -698,4 +720,60 @@ describe('AlertsHistogramPanel', () => { }); }); }); + + describe('when isChartEmbeddablesEnabled = true', () => { + const mockSearchSessionId = 'mockSearchSessionId'; + const mockRefetchByRestartingSession = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + + mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); + + (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ + searchSessionId: mockSearchSessionId, + refetchByRestartingSession: mockRefetchByRestartingSession, + }); + + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + }); + + it('refetch data by refetchByRestartingSession', async () => { + await act(async () => { + mount( + + + + ); + expect((useInspectButton as jest.Mock).mock.calls[0][0].refetch).toEqual( + mockRefetchByRestartingSession + ); + expect((useInspectButton as jest.Mock).mock.calls[0][0].searchSessionId).toEqual( + mockSearchSessionId + ); + }); + }); + + it('renders LensEmbeddable', async () => { + await act(async () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="lens-embeddable"]').exists()).toBeTruthy(); + }); + }); + + it('should skip calling getAlertsRiskQuery', async () => { + await act(async () => { + mount( + + + + ); + expect(mockUseQueryAlerts.mock.calls[0][0].skip).toBeTruthy(); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index 688161019732d5..4b6ecd86383076 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -6,6 +6,7 @@ */ import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; +import type { Action } from '@kbn/ui-actions-plugin/public'; import type { Position } from '@elastic/charts'; import type { EuiComboBox, EuiTitleSize } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiToolTip } from '@elastic/eui'; @@ -50,6 +51,13 @@ import { KpiPanel, StackByComboBox } from '../common/components'; import { useInspectButton } from '../common/hooks'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { GROUP_BY_TOP_LABEL } from '../common/translations'; +import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { getAlertsHistogramLensAttributes as getLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import type { EmbeddableData } from '../../../../common/components/visualization_actions/types'; const defaultTotalAlertsObj: AlertsTotal = { value: 0, @@ -68,6 +76,8 @@ const OptionsFlexItem = styled(EuiFlexItem)` export const LEGEND_WITH_COUNTS_WIDTH = 300; // px +const ChartHeight = '170px'; + interface AlertsHistogramPanelProps { alignHeader?: 'center' | 'baseline' | 'stretch' | 'flexStart' | 'flexEnd'; chartHeight?: number; @@ -75,31 +85,32 @@ interface AlertsHistogramPanelProps { combinedQueries?: string; comboboxRef?: React.RefObject>; defaultStackByOption?: string; + extraActions?: Action[]; filters?: Filter[]; headerChildren?: React.ReactNode; inspectTitle?: string; + legendPosition?: Position; onFieldSelected?: (field: string) => void; /** Override all defaults, and only display this field */ onlyField?: AlertsStackByField; paddingSize?: 's' | 'm' | 'l' | 'none'; panelHeight?: number; - titleSize?: EuiTitleSize; query?: Query; - legendPosition?: Position; + runtimeMappings?: MappingRuntimeFields; setComboboxInputRef?: (inputRef: HTMLInputElement | null) => void; - signalIndexName: string | null; showCountsInLegend?: boolean; showGroupByPlaceholder?: boolean; showLegend?: boolean; showLinkToAlerts?: boolean; - showTotalAlertsCount?: boolean; showStackBy?: boolean; + showTotalAlertsCount?: boolean; + signalIndexName: string | null; stackByLabel?: string; stackByWidth?: number; timelineId?: string; title?: React.ReactNode; + titleSize?: EuiTitleSize; updateDateRange: UpdateDateRange; - runtimeMappings?: MappingRuntimeFields; } const NO_LEGEND_DATA: LegendItem[] = []; @@ -112,30 +123,31 @@ export const AlertsHistogramPanel = memo( combinedQueries, comboboxRef, defaultStackByOption = DEFAULT_STACK_BY_FIELD, + extraActions, filters, headerChildren, inspectTitle, + legendPosition = 'right', onFieldSelected, onlyField, paddingSize = 'm', panelHeight = PANEL_HEIGHT, query, - legendPosition = 'right', + runtimeMappings, setComboboxInputRef, - signalIndexName, showCountsInLegend = false, showGroupByPlaceholder = false, showLegend = true, showLinkToAlerts = false, - showTotalAlertsCount = false, showStackBy = true, + showTotalAlertsCount = false, + signalIndexName, stackByLabel, stackByWidth, timelineId, title = i18n.HISTOGRAM_HEADER, - updateDateRange, titleSize = 'm', - runtimeMappings, + updateDateRange, }) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(false); @@ -168,13 +180,27 @@ export const AlertsHistogramPanel = memo( setQuerySkip(!toggleStatus); }, [toggleStatus]); const toggleQuery = useCallback( - (status: boolean) => { - setToggleStatus(status); + (newToggleStatus: boolean) => { + setToggleStatus(newToggleStatus); // toggle on = skipQuery false - setQuerySkip(!status); + setQuerySkip(!newToggleStatus); }, [setQuerySkip, setToggleStatus] ); + + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const timerange = useMemo(() => ({ from, to }), [from, to]); + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: uniqueQueryId, + }); + const extraVisualizationOptions = useMemo( + () => ({ + filters, + }), + [filters] + ); + const { loading: isLoadingAlerts, data: alertsData, @@ -191,7 +217,7 @@ export const AlertsHistogramPanel = memo( runtimeMappings ), indexName: signalIndexName, - skip: querySkip, + skip: querySkip || isChartEmbeddablesEnabled, queryName: ALERTS_QUERY_NAMES.HISTOGRAM, }); @@ -258,15 +284,29 @@ export const AlertsHistogramPanel = memo( }, [isInitialLoading, isLoadingAlerts, setIsInitialLoading]); useInspectButton({ - setQuery, - response, - request, - refetch, - uniqueQueryId, deleteQuery, loading: isLoadingAlerts, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + request, + response, + searchSessionId, + setQuery, + uniqueQueryId, }); + const onEmbeddableLoad = useCallback( + ({ requests, responses, isLoading }: EmbeddableData) => { + setQuery({ + id: uniqueQueryId, + searchSessionId, + refetch: refetchByRestartingSession, + loading: isLoading, + inspect: { dsl: requests, response: responses }, + }); + }, + [refetchByRestartingSession, searchSessionId, setQuery, uniqueQueryId] + ); + useEffect(() => { setTotalAlertsObj( alertsData?.hits.total ?? { @@ -350,7 +390,7 @@ export const AlertsHistogramPanel = memo( titleSize={titleSize} toggleStatus={toggleStatus} toggleQuery={toggleQuery} - showInspectButton={chartOptionsContextMenu == null} + showInspectButton={isChartEmbeddablesEnabled ? false : chartOptionsContextMenu == null} subtitle={!isInitialLoading && showTotalAlertsCount && totalAlerts} isInspectDisabled={isInspectDisabled} hideSubtitle @@ -390,7 +430,7 @@ export const AlertsHistogramPanel = memo( )} {headerChildren != null && headerChildren} - {chartOptionsContextMenu != null && ( + {chartOptionsContextMenu != null && !isChartEmbeddablesEnabled && ( {chartOptionsContextMenu(uniqueQueryId)} @@ -401,7 +441,21 @@ export const AlertsHistogramPanel = memo( {toggleStatus ? ( - isInitialLoading ? ( + isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( + + ) : isInitialLoading ? ( ) : ( void) | null; uniqueQueryId: string; loading: boolean; + searchSessionId?: string; } /** @@ -33,6 +34,7 @@ export const useInspectButton = ({ uniqueQueryId, deleteQuery, loading, + searchSessionId, }: UseInspectButtonParams) => { useEffect(() => { if (refetch != null && setQuery != null) { @@ -44,6 +46,7 @@ export const useInspectButton = ({ }, loading, refetch, + searchSessionId, }); } @@ -52,7 +55,7 @@ export const useInspectButton = ({ deleteQuery({ id: uniqueQueryId }); } }; - }, [setQuery, loading, response, request, refetch, uniqueQueryId, deleteQuery]); + }, [setQuery, loading, response, request, refetch, uniqueQueryId, deleteQuery, searchSessionId]); }; export function getAggregatableFields(fields: { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx index d4b2fee185cef2..54c35609753c68 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx @@ -38,6 +38,11 @@ import { useGlobalFullScreen } from '../../../../common/containers/use_full_scre import type { TimeframePreviewOptions } from '../../../pages/detection_engine/rules/types'; import { useLicense } from '../../../../common/hooks/use_license'; import { useKibana } from '../../../../common/lib/kibana'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; +import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; +import { getRulePreviewLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/rule_preview'; const LoadingChart = styled(EuiLoadingChart)` display: block; @@ -53,6 +58,8 @@ const FullScreenContainer = styled.div<{ $isFullScreen: boolean }>` export const ID = 'previewHistogram'; +export const CHART_HEIGHT = 150; + interface PreviewHistogramProps { previewId: string; addNoiseWarning: () => void; @@ -89,6 +96,20 @@ export const PreviewHistogram = ({ const isEqlRule = useMemo(() => ruleType === 'eql', [ruleType]); const isMlRule = useMemo(() => ruleType === 'machine_learning', [ruleType]); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const timerange = useMemo(() => ({ from: startDate, to: endDate }), [startDate, endDate]); + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId: `${ID}-${previewId}`, + }); + const extraVisualizationOptions = useMemo( + () => ({ + ruleId: previewId, + spaceId, + }), + [previewId, spaceId] + ); + const [isLoading, { data, inspect, totalCount, refetch }] = usePreviewHistogram({ previewId, startDate, @@ -96,6 +117,7 @@ export const PreviewHistogram = ({ spaceId, indexPattern, ruleType, + skip: isChartEmbeddablesEnabled, }); const license = useLicense(); const { browserFields, runtimeMappings } = useSourcererDataView(SourcererScopeName.detections); @@ -113,9 +135,25 @@ export const PreviewHistogram = ({ useEffect((): void => { if (!isLoading && !isInitializing) { - setQuery({ id: `${ID}-${previewId}`, inspect, loading: isLoading, refetch }); + setQuery({ + id: `${ID}-${previewId}`, + inspect, + loading: isLoading, + refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + searchSessionId, + }); } - }, [setQuery, inspect, isLoading, isInitializing, refetch, previewId]); + }, [ + setQuery, + inspect, + isLoading, + isInitializing, + refetch, + previewId, + isChartEmbeddablesEnabled, + refetchByRestartingSession, + searchSessionId, + ]); const barConfig = useMemo( (): ChartSeriesConfigs => getHistogramConfig(endDate, startDate, !isEqlRule), @@ -161,11 +199,23 @@ export const PreviewHistogram = ({ id={`${ID}-${previewId}`} title={i18n.QUERY_GRAPH_HITS_TITLE} titleSize="xs" + showInspectButton={!isChartEmbeddablesEnabled} /> {isLoading ? ( + ) : isChartEmbeddablesEnabled ? ( + ) : ( { const { uiSettings } = useKibana().services; @@ -55,9 +57,9 @@ export const usePreviewHistogram = ({ stackByField, startDate, includeMissingData: false, - skip: error != null, + skip: skip || error != null, }; - }, [startDate, endDate, filterQuery, spaceId, error, stackByField]); + }, [endDate, filterQuery, spaceId, stackByField, startDate, skip, error]); return useMatrixHistogramCombined(matrixHistogramRequest); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx index 4cef9f95dcc6e8..f87b1b3fcbbf79 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.test.tsx @@ -9,6 +9,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import React from 'react'; import { useAlertsLocalStorage } from './alerts_local_storage'; +import type { Status } from '../../../../../common/detection_engine/schemas/common'; import { RESET_GROUP_BY_FIELDS } from '../../../../common/components/chart_settings_popover/configurations/default/translations'; import { CHART_SETTINGS_POPOVER_ARIA_LABEL } from '../../../../common/components/chart_settings_popover/translations'; import { mockBrowserFields } from '../../../../common/containers/source/mock'; @@ -17,9 +18,16 @@ import { TestProviders } from '../../../../common/mock'; import { ChartPanels } from '.'; jest.mock('./alerts_local_storage'); - jest.mock('../../../../common/containers/sourcerer'); +jest.mock('../../../../common/components/visualization_actions/lens_embeddable'); +jest.mock('../../../../common/components/page/use_refetch_by_session', () => ({ + useRefetchByRestartingSession: jest.fn().mockReturnValue({ + searchSessionId: 'mockSearchSessionId', + refetchByRestartingSession: jest.fn(), + }), +})); + jest.mock('react-router-dom', () => { const originalModule = jest.requireActual('react-router-dom'); @@ -104,6 +112,7 @@ const defaultProps = { }, }, ], + filterGroup: 'open' as Status, isLoadingIndexPattern: false, query: { query: '', @@ -111,6 +120,8 @@ const defaultProps = { }, runtimeMappings: {}, signalIndexName: '.alerts-security.alerts-default', + showBuildingBlockAlerts: false, + showOnlyThreatIndicatorAlerts: false, updateDateRangeCallback: jest.fn(), }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx index ddfe14d51201f8..ee6ba3d0efd356 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx @@ -6,6 +6,7 @@ */ import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import type { Filter, Query } from '@kbn/es-query'; import { EuiFlexItem, EuiLoadingContent, EuiLoadingSpinner } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; @@ -28,6 +29,7 @@ import { } from '../../../components/alerts_kpis/common/config'; import { AlertsCountPanel } from '../../../components/alerts_kpis/alerts_count_panel'; import { GROUP_BY_LABEL } from '../../../components/alerts_kpis/common/translations'; +import { RESET_GROUP_BY_FIELDS } from '../../../../common/components/chart_settings_popover/configurations/default/translations'; const TABLE_PANEL_HEIGHT = 330; // px const TRENT_CHART_HEIGHT = 127; // px @@ -112,6 +114,35 @@ const ChartPanelsComponent: React.FC = ({ onResetStackByField1(); }, [onResetStackByField0, onResetStackByField1]); + const resetGroupByFieldAction = useMemo( + () => [ + { + id: 'resetGroupByField', + + getDisplayName(context: ActionExecutionContext): string { + return RESET_GROUP_BY_FIELDS; + }, + getIconType(context: ActionExecutionContext): string | undefined { + return 'editorRedo'; + }, + type: 'actionButton', + async isCompatible(context: ActionExecutionContext): Promise { + return true; + }, + async execute(context: ActionExecutionContext): Promise { + onReset(); + updateCommonStackBy0(DEFAULT_STACK_BY_FIELD); + + if (updateCommonStackBy1 != null) { + updateCommonStackBy1(DEFAULT_STACK_BY_FIELD1); + } + }, + order: 0, + }, + ], + [onReset, updateCommonStackBy0, updateCommonStackBy1] + ); + const chartOptionsContextMenu = useCallback( (queryId: string) => ( = ({ [alertViewSelection, setAlertViewSelection] ); const isAlertsPageChartsEnabled = useIsExperimentalFeatureEnabled('alertsPageChartsEnabled'); + return (
{alertViewSelection === 'trend' && ( @@ -151,21 +183,22 @@ const ChartPanelsComponent: React.FC = ({ chartOptionsContextMenu={chartOptionsContextMenu} comboboxRef={stackByField0ComboboxRef} defaultStackByOption={trendChartStackBy} + extraActions={resetGroupByFieldAction} filters={alertsHistogramDefaultFilters} inspectTitle={i18n.TREND} - setComboboxInputRef={setStackByField0ComboboxInputRef} onFieldSelected={updateCommonStackBy0} panelHeight={TREND_CHART_PANEL_HEIGHT} query={query} + runtimeMappings={runtimeMappings} + setComboboxInputRef={setStackByField0ComboboxInputRef} showCountsInLegend={true} - showGroupByPlaceholder={true} + showGroupByPlaceholder={false} showTotalAlertsCount={false} + signalIndexName={signalIndexName} stackByLabel={GROUP_BY_LABEL} title={title} titleSize={'s'} - signalIndexName={signalIndexName} updateDateRange={updateDateRangeCallback} - runtimeMappings={runtimeMappings} /> )} @@ -179,6 +212,7 @@ const ChartPanelsComponent: React.FC = ({ = ({ runtimeMappings={runtimeMappings} setStackByField0={updateCommonStackBy0} setStackByField0ComboboxInputRef={setStackByField0ComboboxInputRef} - stackByField0ComboboxRef={stackByField0ComboboxRef} setStackByField1={updateCommonStackBy1} setStackByField1ComboboxInputRef={setStackByField1ComboboxInputRef} - stackByField1ComboboxRef={stackByField1ComboboxRef} signalIndexName={signalIndexName} stackByField0={countTableStackBy0} + stackByField0ComboboxRef={stackByField0ComboboxRef} stackByField1={countTableStackBy1} + stackByField1ComboboxRef={stackByField1ComboboxRef} title={title} /> )} @@ -208,23 +242,24 @@ const ChartPanelsComponent: React.FC = ({ addFilter={addFilter} alignHeader="flexStart" chartOptionsContextMenu={chartOptionsContextMenu} + extraActions={resetGroupByFieldAction} + filters={alertsHistogramDefaultFilters} inspectTitle={i18n.TREEMAP} isPanelExpanded={isTreemapPanelExpanded} - filters={alertsHistogramDefaultFilters} query={query} + riskSubAggregationField="kibana.alert.risk_score" + runtimeMappings={runtimeMappings} setIsPanelExpanded={setIsTreemapPanelExpanded} setStackByField0={updateCommonStackBy0} setStackByField0ComboboxInputRef={setStackByField0ComboboxInputRef} - stackByField0ComboboxRef={stackByField0ComboboxRef} setStackByField1={updateCommonStackBy1} setStackByField1ComboboxInputRef={setStackByField1ComboboxInputRef} - stackByField1ComboboxRef={stackByField1ComboboxRef} signalIndexName={signalIndexName} stackByField0={riskChartStackBy0} + stackByField0ComboboxRef={stackByField0ComboboxRef} stackByField1={riskChartStackBy1} + stackByField1ComboboxRef={stackByField1ComboboxRef} title={title} - riskSubAggregationField="kibana.alert.risk_score" - runtimeMappings={runtimeMappings} /> )} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index 55430d3e685959..03f600d686a747 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -123,6 +123,14 @@ jest.mock('../../components/alerts_table/timeline_actions/use_bulk_add_to_case_a useBulkAddToCaseActions: jest.fn(() => []), })); +jest.mock('../../../common/components/visualization_actions/lens_embeddable'); +jest.mock('../../../common/components/page/use_refetch_by_session', () => ({ + useRefetchByRestartingSession: jest.fn().mockReturnValue({ + searchSessionId: 'mockSearchSessionId', + refetchByRestartingSession: jest.fn(), + }), +})); + describe('DetectionEnginePageComponent', () => { beforeAll(() => { (useParams as jest.Mock).mockReturnValue({}); diff --git a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.test.tsx b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.test.tsx index f7a7b96272fdb7..6582ffba002a6b 100644 --- a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.test.tsx @@ -16,7 +16,6 @@ import type { LensAttributes } from '../../../common/components/visualization_ac jest.mock('../../../common/components/visualization_actions', () => { return { VisualizationActions: () =>
, - HISTOGRAM_ACTIONS_BUTTON_CLASS: 'histogram-actions-trigger', }; }); diff --git a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.tsx b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.tsx index 1cde93e7793ac0..af659d1d076331 100644 --- a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric.tsx @@ -8,12 +8,10 @@ import { EuiFlexGroup, EuiIcon } from '@elastic/eui'; import React from 'react'; import type { StatItem } from './types'; import { HoverVisibilityContainer } from '../../../common/components/hover_visibility_container'; -import { - VisualizationActions, - HISTOGRAM_ACTIONS_BUTTON_CLASS, -} from '../../../common/components/visualization_actions'; +import { VisualizationActions } from '../../../common/components/visualization_actions'; import { FlexItem, StatValue } from './utils'; import { getEmptyTagValue } from '../../../common/components/empty_value'; +import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../../../common/components/visualization_actions/utils'; export interface MetricProps { fields: StatItem[]; @@ -51,7 +49,7 @@ const MetricComponent = ({ )} - +

{field.value != null ? field.value.toLocaleString() : getEmptyTagValue()}{' '} diff --git a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric_embeddable.test.tsx b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric_embeddable.test.tsx index 0b7840260493b5..b801102b6739a3 100644 --- a/x-pack/plugins/security_solution/public/explore/components/stat_items/metric_embeddable.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/stat_items/metric_embeddable.test.tsx @@ -16,7 +16,6 @@ import type { LensAttributes } from '../../../common/components/visualization_ac jest.mock('../../../common/components/visualization_actions', () => { return { VisualizationActions: () =>

, - HISTOGRAM_ACTIONS_BUTTON_CLASS: 'histogram-actions-trigger', }; }); diff --git a/x-pack/plugins/security_solution/public/explore/components/stat_items/stat_items.test.tsx b/x-pack/plugins/security_solution/public/explore/components/stat_items/stat_items.test.tsx index a677805a50cead..39682dc3301aa9 100644 --- a/x-pack/plugins/security_solution/public/explore/components/stat_items/stat_items.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/stat_items/stat_items.test.tsx @@ -43,7 +43,6 @@ jest.mock('../../../common/components/charts/barchart', () => { jest.mock('../../../common/components/visualization_actions', () => { return { VisualizationActions: () =>
, - HISTOGRAM_ACTIONS_BUTTON_CLASS: 'histogram-actions-trigger', }; }); diff --git a/x-pack/plugins/security_solution/public/explore/users/pages/navigation/authentications_query_tab_body.test.tsx b/x-pack/plugins/security_solution/public/explore/users/pages/navigation/authentications_query_tab_body.test.tsx index 5a62bce6b9c3af..7d860e5a99611b 100644 --- a/x-pack/plugins/security_solution/public/explore/users/pages/navigation/authentications_query_tab_body.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/pages/navigation/authentications_query_tab_body.test.tsx @@ -17,6 +17,9 @@ jest.mock('../../../containers/authentications'); jest.mock('../../../../common/containers/query_toggle'); jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../../common/components/visualization_actions'); +jest.mock('../../../../common/components/visualization_actions/lens_embeddable'); + describe('Authentications query tab body', () => { const mockUseAuthentications = useAuthentications as jest.Mock; const mockUseQueryToggle = useQueryToggle as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx new file mode 100644 index 00000000000000..9ebac08c8b363e --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx @@ -0,0 +1,97 @@ +/* + * 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, { useCallback, useEffect, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; +import { DonutChartWrapper } from '../../../../common/components/charts/donutchart'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; +import { getAlertsByStatusAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut'; +import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; +import type { EmbeddableData } from '../../../../common/components/visualization_actions/types'; +import { parseVisualizationData } from '../../../../common/components/visualization_actions/utils'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { inputsActions, inputsSelectors } from '../../../../common/store/inputs'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { ChartLabel } from './chart_label'; +import type { AlertDonutEmbeddableProps, VisualizationAlertsByStatusResponse } from './types'; +import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from './types'; + +const ChartSize = '135px'; + +const AlertDonutEmbeddableComponent: React.FC = ({ + status, + timerange, + label, +}) => { + const dispatch = useDispatch(); + const queryId = `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-${status}`; + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId, + }); + const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); + const { inspect } = useDeepEqualSelector((state) => getGlobalQuery(state, queryId)); + const visualizationData = inspect?.response + ? parseVisualizationData(inspect?.response) + : null; + + const onEmbeddableLoad = useCallback( + ({ requests, responses, isLoading }: EmbeddableData) => { + dispatch( + inputsActions.setQuery({ + inputId: InputsModelId.global, + id: queryId, + searchSessionId, + refetch: refetchByRestartingSession, + loading: isLoading, + inspect: { dsl: requests, response: responses }, + }) + ); + }, + [dispatch, queryId, refetchByRestartingSession, searchSessionId] + ); + + const extraOptions = useMemo(() => ({ status }), [status]); + + useEffect(() => { + dispatch( + inputsActions.setQuery({ + inputId: InputsModelId.global, + id: queryId, + searchSessionId, + refetch: refetchByRestartingSession, + loading: false, + inspect: null, + }) + ); + }, [dispatch, queryId, refetchByRestartingSession, searchSessionId, status]); + + const dataExists = visualizationData != null && visualizationData[0].hits.total !== 0; + + return ( + : null} + > + + + ); +}; + +export const AlertDonutEmbeddable = React.memo(AlertDonutEmbeddableComponent); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx index 751be9e6cbaec4..1032dd2d4e95c4 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx @@ -53,6 +53,11 @@ import { emptyDonutColor } from '../../../../common/components/charts/donutchart import { LastUpdatedAt } from '../../../../common/components/last_updated_at'; import { LinkButton, useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; import { useNavigateToTimeline } from '../hooks/use_navigate_to_timeline'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { useGlobalTime } from '../../../../common/containers/use_global_time'; +import { AlertDonutEmbeddable } from './alert_donut_embeddable'; +import { useAlertsByStatusVisualizationData } from './use_alerts_by_status_visualization_data'; +import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from './types'; const StyledFlexItem = styled(EuiFlexItem)` padding: 0 4px; @@ -64,9 +69,9 @@ const StyledLegendFlexItem = styled(EuiFlexItem)` `; interface AlertsByStatusProps { - signalIndexName: string | null; - entityFilter?: EntityFilter; additionalFilters?: ESBoolQuery[]; + entityFilter?: EntityFilter; + signalIndexName: string | null; } const legendField = 'kibana.alert.severity'; @@ -76,7 +81,6 @@ const chartConfigs: Array<{ key: Severity; label: string; color: string }> = [ { key: 'medium', label: STATUS_MEDIUM_LABEL, color: SEVERITY_COLOR.medium }, { key: 'low', label: STATUS_LOW_LABEL, color: SEVERITY_COLOR.low }, ]; -const DETECTION_RESPONSE_ALERTS_BY_STATUS_ID = 'detection-response-alerts-by-status'; const eventKindSignalFilter: EntityFilter = { field: 'event.kind', @@ -94,6 +98,10 @@ export const AlertsByStatus = ({ deepLinkId: SecurityPageName.alerts, }); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const { to, from } = useGlobalTime(); + const timerange = useMemo(() => ({ from, to }), [from, to]); + const isLargerBreakpoint = useIsWithinMinBreakpoint('xl'); const isSmallBreakpoint = useIsWithinMaxBreakpoint('s'); const donutHeight = isSmallBreakpoint || isLargerBreakpoint ? 120 : 90; @@ -117,8 +125,10 @@ export const AlertsByStatus = ({ additionalFilters, entityFilter, signalIndexName, - skip: !toggleStatus, + skip: !toggleStatus || isChartEmbeddablesEnabled, queryId: DETECTION_RESPONSE_ALERTS_BY_STATUS_ID, + to, + from, }); const legendItems: LegendItem[] = useMemo( () => @@ -137,6 +147,10 @@ export const AlertsByStatus = ({ const totalAlerts = loading || donutData == null ? 0 : openCount + acknowledgedCount + closedCount; + const { total: visualizationTotalAlerts } = useAlertsByStatusVisualizationData(); + + const totalAlertsCount = isChartEmbeddablesEnabled ? visualizationTotalAlerts : totalAlerts; + const fillColor: FillColor = useCallback((d: ShapeTreeNode) => { return chartConfigs.find((cfg) => cfg.label === d.dataName)?.color ?? emptyDonutColor; }, []); @@ -161,6 +175,7 @@ export const AlertsByStatus = ({ inspectMultiple toggleStatus={toggleStatus} toggleQuery={setToggleStatus} + showInspectButton={!isChartEmbeddablesEnabled} > @@ -177,55 +192,88 @@ export const AlertsByStatus = ({ {toggleStatus && ( <> - - {totalAlerts !== 0 && ( - - <> - - - - <> - {ALERTS(totalAlerts)} - - - )} + + {totalAlerts !== 0 || + (visualizationTotalAlerts !== 0 && ( + + <> + + + + <> + {ALERTS(totalAlertsCount)} + + + ))} - - } - totalCount={openCount} - /> + + {isChartEmbeddablesEnabled ? ( + + ) : ( + } + totalCount={openCount} + isChartEmbeddablesEnabled={isChartEmbeddablesEnabled} + /> + )} - - } - totalCount={acknowledgedCount} - /> + + {isChartEmbeddablesEnabled ? ( + + ) : ( + } + totalCount={acknowledgedCount} + isChartEmbeddablesEnabled={isChartEmbeddablesEnabled} + /> + )} - - } - totalCount={closedCount} - /> + + {isChartEmbeddablesEnabled ? ( + + ) : ( + } + totalCount={closedCount} + isChartEmbeddablesEnabled={isChartEmbeddablesEnabled} + /> + )} - - {legendItems.length > 0 && } - + {!isChartEmbeddablesEnabled && ( + + {legendItems.length > 0 && } + + )} diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts index 31e61ae22e6b2c..7310e3ea3716e3 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts @@ -6,7 +6,7 @@ */ import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; +import type { ESQuery } from '../../../../../common/typed_json'; interface StatusBySeverity { doc_count_error_upper_bound: number; @@ -15,7 +15,7 @@ interface StatusBySeverity { } interface StatusBucket { - key: Status; + key: AlertsByStatus; doc_count: number; statusBySeverity?: StatusBySeverity; } @@ -51,11 +51,46 @@ export interface AlertsByStatusResponse }; } +export interface VisualizationAlertsByStatusResponse { + took: number; + _shards: { + total: number; + successful: number; + skipped: number; + failed: number; + }; + aggregations?: Aggregations; + hits: { + total: number; + hits: Hit[]; + }; +} + export interface SeverityBuckets { key: Severity; value: number; label?: string; } export type ParsedAlertsData = Partial< - Record + Record > | null; + +export type AlertsByStatus = 'open' | 'acknowledged' | 'closed'; + +export interface AlertDonutEmbeddableProps { + status: AlertsByStatus; + timerange: { from: string; to: string }; + label: string; +} + +export interface VisualizationAlertsByStatusData { + responses: VisualizationAlertsByStatusResponse[]; + requests: ESQuery[]; + isLoading: boolean; +} +export interface VisualizationInspectQuery { + dsl: ESQuery[]; + response: VisualizationAlertsByStatusResponse[]; +} + +export const DETECTION_RESPONSE_ALERTS_BY_STATUS_ID = 'detection-response-alerts-by-status'; diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx index 4cbaa8bcff4c27..15f933754b68e4 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.test.tsx @@ -47,6 +47,8 @@ const renderUseAlertsByStatus = (props: Partial = {}) => useAlertsByStatus({ queryId: 'test', signalIndexName: 'signal-alerts', + from, + to, ...props, }), { diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.ts index ee7545f3f90237..e80f3ec0a0caf2 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status.ts @@ -8,8 +8,8 @@ import { useCallback, useEffect, useState } from 'react'; import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; +import { useDispatch } from 'react-redux'; import type { ESBoolQuery } from '../../../../../common/typed_json'; -import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { useQueryAlerts } from '../../../../detections/containers/detection_engine/alerts/use_query'; import { ALERTS_QUERY_NAMES } from '../../../../detections/containers/detection_engine/alerts/constants'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; @@ -20,6 +20,9 @@ import { STATUS_LOW_LABEL, STATUS_MEDIUM_LABEL, } from '../translations'; +import { inputsActions } from '../../../../common/store/inputs'; +import type { DeleteQuery, SetQuery } from '../../../../common/containers/use_global_time/types'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; export const severityLabels: Record = { critical: STATUS_CRITICAL_LABEL, @@ -110,6 +113,8 @@ export interface UseAlertsByStatusProps { skip?: boolean; entityFilter?: EntityFilter; additionalFilters?: ESBoolQuery[]; + from: string; + to: string; } export type UseAlertsByStatus = (props: UseAlertsByStatusProps) => { @@ -124,11 +129,32 @@ export const useAlertsByStatus: UseAlertsByStatus = ({ queryId, signalIndexName, skip = false, + to, + from, }) => { - const { to, from, deleteQuery, setQuery } = useGlobalTime(); + const dispatch = useDispatch(); const [updatedAt, setUpdatedAt] = useState(Date.now()); const [items, setItems] = useState(null); - + const setQuery = useCallback( + ({ id, inspect, loading, refetch, searchSessionId }: SetQuery) => + dispatch( + inputsActions.setQuery({ + inputId: InputsModelId.global, + id, + inspect, + loading, + refetch, + searchSessionId, + }) + ), + [dispatch] + ); + + const deleteQuery = useCallback( + ({ id }: DeleteQuery) => + dispatch(inputsActions.deleteOneQuery({ inputId: InputsModelId.global, id })), + [dispatch] + ); const { data, loading: isLoading, diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts new file mode 100644 index 00000000000000..de58feecdabac6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/use_alerts_by_status_visualization_data.ts @@ -0,0 +1,54 @@ +/* + * 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 { parseVisualizationData } from '../../../../common/components/visualization_actions/utils'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { inputsSelectors } from '../../../../common/store/inputs'; +import type { VisualizationAlertsByStatusResponse } from './types'; +import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from './types'; + +export const useAlertsByStatusVisualizationData = () => { + const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); + const { inspect: inspectOpenAlerts } = useDeepEqualSelector((state) => + getGlobalQuery(state, `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-open`) + ); + const { inspect: inspectAcknowledgedAlerts } = useDeepEqualSelector((state) => + getGlobalQuery(state, `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-acknowledged`) + ); + const { inspect: inspectClosedAlerts } = useDeepEqualSelector((state) => + getGlobalQuery(state, `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-closed`) + ); + const visualizationOpenAlertsData = + inspectOpenAlerts != null + ? parseVisualizationData(inspectOpenAlerts?.response)[0] + .hits.total + : 0; + const visualizationAcknowledgedAlertsData = + inspectAcknowledgedAlerts != null + ? parseVisualizationData( + inspectAcknowledgedAlerts?.response + )[0].hits.total + : 0; + const visualizationClosedAlertsData = + inspectClosedAlerts != null + ? parseVisualizationData( + inspectClosedAlerts?.response + )[0].hits.total + : 0; + + const visualizationTotalAlertsData = + visualizationOpenAlertsData + + visualizationAcknowledgedAlertsData + + visualizationClosedAlertsData ?? 0; + + return { + open: visualizationOpenAlertsData, + acknowledged: visualizationAcknowledgedAlertsData, + closed: visualizationClosedAlertsData, + total: visualizationTotalAlertsData, + }; +}; diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx index 8f6f0e7ce11f62..3197066aeab492 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx @@ -50,6 +50,8 @@ jest.mock('../../common/lib/kibana', () => { }); jest.mock('../../common/containers/source'); jest.mock('../../common/containers/sourcerer'); +jest.mock('../../common/components/visualization_actions'); +jest.mock('../../common/components/visualization_actions/lens_embeddable'); jest.mock('../../common/containers/use_global_time', () => ({ useGlobalTime: jest.fn().mockReturnValue({ from: '2020-07-07T08:20:18.966Z', From e0bdf32bdb718e17b81b8119d8234b4a90ba51c9 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 10 Jan 2023 12:40:45 +0000 Subject: [PATCH 33/72] replace donut on alerts page --- .../common/alerts/alerts_by_status_donut.ts | 43 +++++++++++-------- .../alerts_summary_charts_panel/index.tsx | 10 ++++- .../severity_level_chart.test.tsx | 4 ++ .../severity_donut/severity_level_chart.tsx | 40 +++++++++++------ .../severity_donut/use_severity_chart_data.ts | 5 ++- .../alert_donut_embeddable.tsx | 3 +- .../alerts_by_status/types.ts | 6 ++- 7 files changed, 73 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts index 6875a2c7f6b557..7de608419b83ad 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts @@ -38,25 +38,30 @@ export const getAlertsByStatusAttributes: GetLensAttributes = ( language: 'kuery', }, filters: [ - { - meta: { - disabled: false, - negate: false, - alias: null, - index: 'a1aaa83b-5026-444e-9465-50e0afade01c', - key: stackByField, - field: stackByField, - params: { - query: extraOptions?.status, - }, - type: 'phrase', - }, - query: { - match_phrase: { - [stackByField]: extraOptions?.status, - }, - }, - }, + ...(extraOptions?.status && stackByField + ? [ + { + meta: { + disabled: false, + negate: false, + alias: null, + index: 'a1aaa83b-5026-444e-9465-50e0afade01c', + key: stackByField, + field: stackByField, + params: { + query: extraOptions?.status, + }, + type: 'phrase', + }, + query: { + match_phrase: { + [stackByField]: extraOptions?.status, + }, + }, + }, + ] + : []), + ...(extraOptions?.filters ? extraOptions.filters : []), ], datasourceStates: { formBased: { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx index fa2cd5519103a9..d3e1414d4af242 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx @@ -69,7 +69,11 @@ export const AlertsSummaryChartsPanel: React.FC = ({ [setQuerySkip, setToggleStatus] ); - const { items: severityData, isLoading: isSeverityLoading } = useSeverityChartData({ + const { + items: severityData, + isLoading: isSeverityLoading, + timerange, + } = useSeverityChartData({ filters, query, signalIndexName, @@ -99,10 +103,12 @@ export const AlertsSummaryChartsPanel: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.test.tsx index 3b6b262efb545a..bf725f57b69851 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.test.tsx @@ -21,6 +21,10 @@ describe('Severity level chart', () => { data: [], isLoading: false, uniqueQueryId: 'test-query-id', + timerange: { + from: '2023-01-10T00:00:00.000Z', + to: '2023-01-10T23:59:59.999Z', + }, }; afterEach(() => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx index 1183c66735f29c..6d5e291b743d50 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx @@ -8,6 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiInMemoryTable } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { isEmpty } from 'lodash/fp'; +import type { Filter } from '@kbn/es-query'; import { ALERT_SEVERITY } from '@kbn/rule-data-utils'; import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ShapeTreeNode, ElementClickListener } from '@elastic/charts'; @@ -20,22 +21,29 @@ import { HeaderSection } from '../../../../../common/components/header_section'; import { InspectButtonContainer } from '../../../../../common/components/inspect'; import { getSeverityTableColumns } from '../columns'; import { getSeverityColor } from '../helpers'; +import { AlertDonutEmbeddable } from '../../../../../overview/components/detection_response/alerts_by_status/alert_donut_embeddable'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; const DONUT_HEIGHT = 150; interface AlertsChartsPanelProps { + addFilter?: ({ field, value }: { field: string; value: string | number }) => void; data: ParsedSeverityData; + filters?: Filter[]; isLoading: boolean; + timerange: { from: string; to: string }; uniqueQueryId: string; - addFilter?: ({ field, value }: { field: string; value: string | number }) => void; } export const SeverityLevelChart: React.FC = ({ + addFilter, data, + filters, isLoading, + timerange, uniqueQueryId, - addFilter, }) => { + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const fillColor: FillColor = useCallback((d: ShapeTreeNode) => { return getSeverityColor(d.dataName); }, []); @@ -98,16 +106,24 @@ export const SeverityLevelChart: React.FC = ({ /> - } - totalCount={count} - onElementClick={onElementClick} - /> + {isChartEmbeddablesEnabled ? ( + + ) : ( + } + totalCount={count} + onElementClick={onElementClick} + /> + )} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.ts index ce51c78f3f64e3..f8fef66bea3c05 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.ts @@ -72,6 +72,7 @@ export interface UseSeverityChartProps { export type UseAlertsBySeverity = (props: UseSeverityChartProps) => { items: ParsedSeverityData; isLoading: boolean; + timerange: { from: string; to: string }; updatedAt: number; }; @@ -87,7 +88,7 @@ export const useSeverityChartData: UseAlertsBySeverity = ({ const { to, from, deleteQuery, setQuery } = useGlobalTime(); const [updatedAt, setUpdatedAt] = useState(Date.now()); const [items, setItems] = useState(null); - + const timerange = useMemo(() => ({ to, from }), [to, from]); const additionalFilters = useMemo(() => { try { return [ @@ -159,5 +160,5 @@ export const useSeverityChartData: UseAlertsBySeverity = ({ uniqueQueryId, }); - return { items, isLoading, updatedAt }; + return { items, isLoading, updatedAt, timerange }; }; diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx index 9ebac08c8b363e..fb46e43b548d7c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx @@ -23,6 +23,7 @@ import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from './types'; const ChartSize = '135px'; const AlertDonutEmbeddableComponent: React.FC = ({ + filters, status, timerange, label, @@ -55,7 +56,7 @@ const AlertDonutEmbeddableComponent: React.FC = ({ [dispatch, queryId, refetchByRestartingSession, searchSessionId] ); - const extraOptions = useMemo(() => ({ status }), [status]); + const extraOptions = useMemo(() => ({ status, filters }), [status, filters]); useEffect(() => { dispatch( diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts index 7310e3ea3716e3..c2a4ad1215d9db 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts @@ -6,6 +6,7 @@ */ import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { Filter } from '@kbn/es-query'; import type { ESQuery } from '../../../../../common/typed_json'; interface StatusBySeverity { @@ -78,9 +79,10 @@ export type ParsedAlertsData = Partial< export type AlertsByStatus = 'open' | 'acknowledged' | 'closed'; export interface AlertDonutEmbeddableProps { - status: AlertsByStatus; - timerange: { from: string; to: string }; + filters?: Filter[]; label: string; + status?: AlertsByStatus; + timerange: { from: string; to: string }; } export interface VisualizationAlertsByStatusData { From fc3b88645b68b159983250e9017a62935a54081b Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 10 Jan 2023 14:02:01 +0000 Subject: [PATCH 34/72] replace alerts by severity table with lens --- .../common/alerts/alerts_by_severity_table.ts | 127 ++++++++++++++++++ .../alerts_summary_charts_panel/index.tsx | 4 +- .../severity_donut/severity_level_chart.tsx | 28 ++-- .../use_severity_chart_data.test.tsx | 9 +- .../alert_by_severity_table_embeddable.tsx | 59 ++++++++ 5 files changed, 216 insertions(+), 11 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts create mode 100644 x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_by_severity_table_embeddable.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts new file mode 100644 index 00000000000000..6ad552c856df89 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts @@ -0,0 +1,127 @@ +/* + * 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 type { GetLensAttributes } from '../../../types'; + +export const getAlertsBySeverityTableAttributes: GetLensAttributes = ( + stackByField = 'kibana.alert.workflow_status', + extraOptions +) => ({ + title: 'alerts-table', + description: '', + visualizationType: 'lnsDatatable', + state: { + visualization: { + layerId: '51ed355e-6e23-4038-a417-f653a1160370', + layerType: 'data', + columns: [ + { + columnId: 'a9b43606-7ff7-46ae-a47c-85bed80fab9a', + }, + { + columnId: '21cc4a49-3780-4b1a-be28-f02fa5303d24', + }, + ], + headerRowHeight: 'custom', + headerRowHeightLines: 0.6, + rowHeight: 'custom', + rowHeightLines: 0.8, + }, + query: { + query: '', + language: 'kuery', + }, + filters: extraOptions?.filters ? extraOptions.filters : [], + datasourceStates: { + formBased: { + layers: { + '51ed355e-6e23-4038-a417-f653a1160370': { + columns: { + 'a9b43606-7ff7-46ae-a47c-85bed80fab9a': { + label: 'Filters', + dataType: 'string', + operationType: 'filters', + scale: 'ordinal', + isBucketed: true, + params: { + filters: [ + { + input: { + query: 'kibana.alert.severity: "critical"', + language: 'kuery', + }, + label: 'Critical', + }, + { + label: 'High', + input: { + query: 'kibana.alert.severity : "high" ', + language: 'kuery', + }, + }, + { + input: { + query: 'kibana.alert.severity: "medium"', + language: 'kuery', + }, + label: 'Medium', + }, + { + input: { + query: 'kibana.alert.severity : "low" ', + language: 'kuery', + }, + label: 'Low', + }, + ], + }, + }, + '21cc4a49-3780-4b1a-be28-f02fa5303d24': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + filter: { + query: '', + language: 'kuery', + }, + params: { + emptyAsNull: true, + }, + }, + }, + columnOrder: [ + 'a9b43606-7ff7-46ae-a47c-85bed80fab9a', + '21cc4a49-3780-4b1a-be28-f02fa5303d24', + ], + sampling: 1, + incompleteColumns: {}, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [], + adHocDataViews: {}, + }, + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: 'indexpattern-datasource-layer-51ed355e-6e23-4038-a417-f653a1160370', + }, + { + type: 'index-pattern', + name: '22752b9b-cfcd-43f0-a6ee-27dd4893edcf', + id: '{dataViewId}', + }, + ], +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx index d3e1414d4af242..dae2c0200af623 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx @@ -16,6 +16,7 @@ import { HeaderSection } from '../../../../common/components/header_section'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { useSeverityChartData } from './severity_donut/use_severity_chart_data'; import { SeverityLevelChart } from './severity_donut/severity_level_chart'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; const DETECTIONS_ALERTS_CHARTS_ID = 'detections-alerts-charts'; @@ -54,6 +55,7 @@ export const AlertsSummaryChartsPanel: React.FC = ({ }: Props) => { // create a unique, but stable (across re-renders) query id const uniqueQueryId = useMemo(() => `${DETECTIONS_ALERTS_CHARTS_ID}-${uuid.v4()}`, []); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const { toggleStatus, setToggleStatus } = useQueryToggle(DETECTIONS_ALERTS_CHARTS_ID); const [querySkip, setQuerySkip] = useState(!toggleStatus); @@ -78,7 +80,7 @@ export const AlertsSummaryChartsPanel: React.FC = ({ query, signalIndexName, runtimeMappings, - skip: querySkip, + skip: querySkip || isChartEmbeddablesEnabled, uniqueQueryId, }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx index 6d5e291b743d50..16d843c1bfa23c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx @@ -23,6 +23,7 @@ import { getSeverityTableColumns } from '../columns'; import { getSeverityColor } from '../helpers'; import { AlertDonutEmbeddable } from '../../../../../overview/components/detection_response/alerts_by_status/alert_donut_embeddable'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; +import { AlertBySeverityTableEmbeddable } from '../../../../../overview/components/detection_response/alerts_by_status/alert_by_severity_table_embeddable'; const DONUT_HEIGHT = 150; @@ -88,22 +89,31 @@ export const SeverityLevelChart: React.FC = ({ - + - + {isChartEmbeddablesEnabled ? ( + + ) : ( + + )} {isChartEmbeddablesEnabled ? ( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.test.tsx index 0d48d023d06a34..753f6715f923ba 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.test.tsx @@ -15,7 +15,10 @@ import { useSeverityChartData } from './use_severity_chart_data'; const dateNow = new Date('2022-04-08T12:00:00.000Z').valueOf(); const mockDateNow = jest.fn().mockReturnValue(dateNow); Date.now = jest.fn(() => mockDateNow()) as unknown as DateConstructor['now']; - +const timerange = { + from: '2022-04-05T12:00:00.000Z', + to: '2022-04-08T12:00:00.000Z', +}; const defaultUseQueryAlertsReturn = { loading: false, data: null, @@ -68,6 +71,7 @@ describe('useSeverityChartData', () => { items: null, isLoading: false, updatedAt: dateNow, + timerange, }); expect(mockUseQueryAlerts).toBeCalledWith({ @@ -89,6 +93,7 @@ describe('useSeverityChartData', () => { items: parsedAlerts, isLoading: false, updatedAt: dateNow, + timerange, }); }); @@ -109,6 +114,7 @@ describe('useSeverityChartData', () => { items: parsedAlerts, isLoading: false, updatedAt: newDateNow, + timerange, }); }); @@ -126,6 +132,7 @@ describe('useSeverityChartData', () => { items: null, isLoading: false, updatedAt: dateNow, + timerange, }); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_by_severity_table_embeddable.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_by_severity_table_embeddable.tsx new file mode 100644 index 00000000000000..feeff0d5d6c138 --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_by_severity_table_embeddable.tsx @@ -0,0 +1,59 @@ +/* + * 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, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; +import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; +import { getAlertsBySeverityTableAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table'; +import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; +import { inputsActions } from '../../../../common/store/inputs'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import type { AlertDonutEmbeddableProps } from './types'; +import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from './types'; + +const ChartSize = '135px'; + +const AlertBySeverityTableEmbeddableComponent: React.FC = ({ + filters, + timerange, +}) => { + const dispatch = useDispatch(); + const queryId = `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-table`; + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId: InputsModelId.global, + queryId, + }); + + const extraOptions = useMemo(() => ({ filters }), [filters]); + + useEffect(() => { + dispatch( + inputsActions.setQuery({ + inputId: InputsModelId.global, + id: queryId, + searchSessionId, + refetch: refetchByRestartingSession, + loading: false, + inspect: null, + }) + ); + }, [dispatch, queryId, refetchByRestartingSession, searchSessionId]); + + return ( + + ); +}; + +export const AlertBySeverityTableEmbeddable = React.memo(AlertBySeverityTableEmbeddableComponent); From a2c4cbc51b0880ab1548fa7d2c29e9b18ac04462 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 11 Jan 2023 17:45:21 +0000 Subject: [PATCH 35/72] replace donut charts for risk scores --- .../donut_embeddable.tsx | 42 ++++++++ .../common/risk_scores/risk_score_donut.ts | 88 +++++++++++++++++ .../lens_embeddable_managed.tsx | 60 ++++++++++++ .../components/visualization_actions/types.ts | 4 + .../__mocks__/alert_donut_embeddable.tsx | 10 -- .../alert_donut_embeddable.tsx | 97 ------------------- .../alerts_by_status/alerts_by_status.tsx | 58 ++++++++--- .../alerts_by_status/types.ts | 5 +- .../entity_analytics/risk_score/index.tsx | 22 ++++- 9 files changed, 259 insertions(+), 127 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable_managed.tsx delete mode 100644 x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/__mocks__/alert_donut_embeddable.tsx delete mode 100644 x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx new file mode 100644 index 00000000000000..b11473e7852915 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx @@ -0,0 +1,42 @@ +/* + * 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 from 'react'; +import { ChartLabel } from '../../../overview/components/detection_response/alerts_by_status/chart_label'; +import type { + DonutEmbeddableProps, + VisualizationAlertsByStatusResponse, +} from '../../../overview/components/detection_response/alerts_by_status/types'; +import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { inputsSelectors } from '../../store/inputs'; +import { DonutChartWrapper } from '../charts/donutchart'; +import { VisualizationEmbeddable } from './lens_embeddable_managed'; +import { parseVisualizationData } from './utils'; + +const DonutEmbeddableComponent: React.FC = (props) => { + const { label, id, ...lensprops } = props; + const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); + const { inspect } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); + const visualizationData = inspect?.response + ? parseVisualizationData(inspect?.response) + : null; + + const dataExists = visualizationData != null && visualizationData[0]?.hits.total !== 0; + + return ( + : null} + > + + + ); +}; + +export const DonutEmbeddable = React.memo(DonutEmbeddableComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts new file mode 100644 index 00000000000000..af1ab6124a8325 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts @@ -0,0 +1,88 @@ +/* + * 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 type { GetLensAttributes } from '../../../types'; + +export const getRiskScoreDonutAttributes: GetLensAttributes = (stackByField, extraOptions) => ({ + title: `${stackByField} risk donut`, + description: '', + visualizationType: 'lnsPie', + state: { + visualization: { + shape: 'donut', + layers: [ + { + layerId: '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', + primaryGroups: [], + metrics: ['f04a71a3-399f-4d32-9efc-8a005e989991'], + numberDisplay: 'value', + categoryDisplay: 'hide', + legendDisplay: 'hide', + nestedLegend: true, + layerType: 'data', + legendSize: 'xlarge', + legendPosition: 'right', + percentDecimals: 2, + emptySizeRatio: 0.8, + }, + ], + }, + query: { + query: '', + language: 'kuery', + }, + filters: [], + datasourceStates: { + formBased: { + layers: { + '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b': { + columns: { + 'f04a71a3-399f-4d32-9efc-8a005e989991': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + params: { + emptyAsNull: true, + }, + }, + }, + columnOrder: ['f04a71a3-399f-4d32-9efc-8a005e989991'], + sampling: 1, + incompleteColumns: {}, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [ + { + type: 'index-pattern', + id: '40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0', + name: 'indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', + }, + ], + adHocDataViews: { + '40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0': { + id: '40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0', + title: `ml_${stackByField}_risk_score_latest_${extraOptions?.spaceId}`, + timeFieldName: '@timestamp', + sourceFilters: [], + fieldFormats: {}, + runtimeFieldMap: {}, + fieldAttrs: {}, + allowNoIndex: false, + name: `ml_${stackByField}_risk_score_latest_${extraOptions?.spaceId}`, + }, + }, + }, + references: [], +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable_managed.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable_managed.tsx new file mode 100644 index 00000000000000..e787271de29a30 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable_managed.tsx @@ -0,0 +1,60 @@ +/* + * 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, { useCallback, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { inputsActions } from '../../store/inputs'; +import { InputsModelId } from '../../store/inputs/constants'; +import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; +import { LensEmbeddable } from './lens_embeddable'; +import type { EmbeddableData, VisualizationEmbeddableProps } from './types'; + +const VisualizationEmbeddableComponent: React.FC = (props) => { + const dispatch = useDispatch(); + const { inputId = InputsModelId.global, id, onLoad, ...lensPorps } = props; + const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + inputId, + queryId: id, + }); + + const onEmbeddableLoad = useCallback( + ({ requests, responses, isLoading }: EmbeddableData) => { + dispatch( + inputsActions.setQuery({ + inputId, + id, + searchSessionId, + refetch: refetchByRestartingSession, + loading: isLoading, + inspect: { dsl: requests, response: responses }, + }) + ); + + if (typeof onLoad === 'function') { + onLoad({ requests, responses, isLoading }); + } + }, + [dispatch, inputId, onLoad, id, refetchByRestartingSession, searchSessionId] + ); + + useEffect(() => { + dispatch( + inputsActions.setQuery({ + inputId, + id, + searchSessionId, + refetch: refetchByRestartingSession, + loading: false, + inspect: null, + }) + ); + }, [dispatch, inputId, id, refetchByRestartingSession, searchSessionId]); + + return ; +}; + +export const VisualizationEmbeddable = React.memo(VisualizationEmbeddableComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index 8b0a84f0843961..766b4d0a712d16 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -104,3 +104,7 @@ export interface ExtraOptions { spaceId?: string; status?: Status; } + +export interface VisualizationEmbeddableProps extends LensEmbeddableComponentProps { + inputId?: InputsModelId.global | InputsModelId.timeline; +} diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/__mocks__/alert_donut_embeddable.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/__mocks__/alert_donut_embeddable.tsx deleted file mode 100644 index 62343b498ea3e0..00000000000000 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/__mocks__/alert_donut_embeddable.tsx +++ /dev/null @@ -1,10 +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 from 'react'; - -export const AlertDonutEmbeddable = () =>
; diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx deleted file mode 100644 index 9ebac08c8b363e..00000000000000 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_donut_embeddable.tsx +++ /dev/null @@ -1,97 +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, { useCallback, useEffect, useMemo } from 'react'; -import { useDispatch } from 'react-redux'; -import { DonutChartWrapper } from '../../../../common/components/charts/donutchart'; -import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; -import { getAlertsByStatusAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut'; -import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; -import type { EmbeddableData } from '../../../../common/components/visualization_actions/types'; -import { parseVisualizationData } from '../../../../common/components/visualization_actions/utils'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { inputsActions, inputsSelectors } from '../../../../common/store/inputs'; -import { InputsModelId } from '../../../../common/store/inputs/constants'; -import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import { ChartLabel } from './chart_label'; -import type { AlertDonutEmbeddableProps, VisualizationAlertsByStatusResponse } from './types'; -import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from './types'; - -const ChartSize = '135px'; - -const AlertDonutEmbeddableComponent: React.FC = ({ - status, - timerange, - label, -}) => { - const dispatch = useDispatch(); - const queryId = `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-${status}`; - const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ - inputId: InputsModelId.global, - queryId, - }); - const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); - const { inspect } = useDeepEqualSelector((state) => getGlobalQuery(state, queryId)); - const visualizationData = inspect?.response - ? parseVisualizationData(inspect?.response) - : null; - - const onEmbeddableLoad = useCallback( - ({ requests, responses, isLoading }: EmbeddableData) => { - dispatch( - inputsActions.setQuery({ - inputId: InputsModelId.global, - id: queryId, - searchSessionId, - refetch: refetchByRestartingSession, - loading: isLoading, - inspect: { dsl: requests, response: responses }, - }) - ); - }, - [dispatch, queryId, refetchByRestartingSession, searchSessionId] - ); - - const extraOptions = useMemo(() => ({ status }), [status]); - - useEffect(() => { - dispatch( - inputsActions.setQuery({ - inputId: InputsModelId.global, - id: queryId, - searchSessionId, - refetch: refetchByRestartingSession, - loading: false, - inspect: null, - }) - ); - }, [dispatch, queryId, refetchByRestartingSession, searchSessionId, status]); - - const dataExists = visualizationData != null && visualizationData[0].hits.total !== 0; - - return ( - : null} - > - - - ); -}; - -export const AlertDonutEmbeddable = React.memo(AlertDonutEmbeddableComponent); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx index 1032dd2d4e95c4..5ff83782f2e597 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx @@ -55,9 +55,12 @@ import { LinkButton, useGetSecuritySolutionLinkProps } from '../../../../common/ import { useNavigateToTimeline } from '../hooks/use_navigate_to_timeline'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; -import { AlertDonutEmbeddable } from './alert_donut_embeddable'; import { useAlertsByStatusVisualizationData } from './use_alerts_by_status_visualization_data'; import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from './types'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { DonutEmbeddable } from '../../../../common/components/visualization_actions/donut_embeddable'; +import type { Status } from '../../../../../common/detection_engine/schemas/common'; +import { getAlertsByStatusAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut'; const StyledFlexItem = styled(EuiFlexItem)` padding: 0 4px; @@ -68,6 +71,8 @@ const StyledLegendFlexItem = styled(EuiFlexItem)` padding-top: 45px; `; +const ChartSize = '135px'; + interface AlertsByStatusProps { additionalFilters?: ESBoolQuery[]; entityFilter?: EntityFilter; @@ -87,6 +92,10 @@ const eventKindSignalFilter: EntityFilter = { value: 'signal', }; +const openDonutOptions = { status: 'open' as Status }; +const acknowledgedDonutOptions = { status: 'acknowledged' as Status }; +const closedDonutOptions = { status: 'closed' as Status }; + export const AlertsByStatus = ({ additionalFilters, signalIndexName, @@ -193,9 +202,9 @@ export const AlertsByStatus = ({ <> - {totalAlerts !== 0 || - (visualizationTotalAlerts !== 0 && ( - + + {totalAlerts !== 0 || + (visualizationTotalAlerts !== 0 && ( <> @@ -203,16 +212,23 @@ export const AlertsByStatus = ({ <> {ALERTS(totalAlertsCount)} - - ))} + ))} + + {isChartEmbeddablesEnabled ? ( - ) : ( {isChartEmbeddablesEnabled ? ( - ) : ( {isChartEmbeddablesEnabled ? ( - ) : ( (Date.now()); const dispatch = useDispatch(); - + const spaceId = useSpaceId(); + const extraOptions = useMemo(() => ({ spaceId }), [spaceId]); const entity = useMemo( () => riskEntity === RiskScoreEntity.host @@ -137,6 +142,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc }), [from, to] ); + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const { severityCount, @@ -270,7 +276,19 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc {toggleStatus && ( - + {isChartEmbeddablesEnabled && spaceId ? ( + + ) : ( + + )} Date: Thu, 12 Jan 2023 12:29:42 +0000 Subject: [PATCH 36/72] fix index nout found warning --- .../components/entity_analytics/risk_score/index.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx index 1d6978d9c3ffb8..f0835ba32befb4 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx @@ -47,12 +47,14 @@ import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_ex import { DonutEmbeddable } from '../../../../common/components/visualization_actions/donut_embeddable'; import { useSpaceId } from '../../../../common/hooks/use_space_id'; import { getRiskScoreDonutAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut'; +import { TOTAL_LABEL } from '../common/translations'; const HOST_RISK_TABLE_QUERY_ID = 'hostRiskDashboardTable'; const HOST_RISK_KPI_QUERY_ID = 'headerHostRiskScoreKpiQuery'; const USER_RISK_TABLE_QUERY_ID = 'userRiskDashboardTable'; const USER_RISK_KPI_QUERY_ID = 'headerUserRiskScoreKpiQuery'; +// eslint-disable-next-line complexity const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskScoreEntity }) => { const { deleteQuery, setQuery, from, to } = useGlobalTime(); const [updatedAt, setUpdatedAt] = useState(Date.now()); @@ -276,15 +278,15 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc {toggleStatus && ( - {isChartEmbeddablesEnabled && spaceId ? ( + {isChartEmbeddablesEnabled && spaceId && data && data.length > 0 ? ( ) : ( From 0284f435dcd31b434f5dba32445e23532ed368b3 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 12 Jan 2023 12:50:04 +0000 Subject: [PATCH 37/72] sync with main --- .../visualization_actions/lens_embeddable_managed.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable_managed.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable_managed.tsx index e787271de29a30..ea05c7d0b9e47b 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable_managed.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable_managed.tsx @@ -16,7 +16,7 @@ import type { EmbeddableData, VisualizationEmbeddableProps } from './types'; const VisualizationEmbeddableComponent: React.FC = (props) => { const dispatch = useDispatch(); const { inputId = InputsModelId.global, id, onLoad, ...lensPorps } = props; - const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ + const { session, refetchByRestartingSession } = useRefetchByRestartingSession({ inputId, queryId: id, }); @@ -27,7 +27,7 @@ const VisualizationEmbeddableComponent: React.FC = inputsActions.setQuery({ inputId, id, - searchSessionId, + searchSessionId: session.current.start(), refetch: refetchByRestartingSession, loading: isLoading, inspect: { dsl: requests, response: responses }, @@ -38,7 +38,7 @@ const VisualizationEmbeddableComponent: React.FC = onLoad({ requests, responses, isLoading }); } }, - [dispatch, inputId, onLoad, id, refetchByRestartingSession, searchSessionId] + [dispatch, inputId, id, session, refetchByRestartingSession, onLoad] ); useEffect(() => { @@ -46,13 +46,13 @@ const VisualizationEmbeddableComponent: React.FC = inputsActions.setQuery({ inputId, id, - searchSessionId, + searchSessionId: session.current.start(), refetch: refetchByRestartingSession, loading: false, inspect: null, }) ); - }, [dispatch, inputId, id, refetchByRestartingSession, searchSessionId]); + }, [dispatch, inputId, id, refetchByRestartingSession, session]); return ; }; From 0ac2d886faf5c3c0b591c7512f9af05b108875d0 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 12 Jan 2023 15:10:31 +0000 Subject: [PATCH 38/72] fix eslint --- .../__mocks__/visualization_embeddable.tsx | 12 ++ .../donut_embeddable.tsx | 2 +- .../risk_score_donut.test.ts.snap | 105 ++++++++++++++++ .../risk_scores/risk_score_donut.test.ts | 49 ++++++++ .../common/risk_scores/risk_score_donut.ts | 9 +- ...naged.tsx => visualization_embeddable.tsx} | 6 + .../alerts_by_status.test.tsx | 5 +- .../alerts_by_status/alerts_by_status.tsx | 4 +- .../risk_score/header_content.tsx | 72 +++++++++++ .../entity_analytics/risk_score/index.tsx | 113 +++--------------- .../entity_analytics/risk_score/use_entity.ts | 62 ++++++++++ 11 files changed, 335 insertions(+), 104 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/visualization_embeddable.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.test.ts rename x-pack/plugins/security_solution/public/common/components/visualization_actions/{lens_embeddable_managed.tsx => visualization_embeddable.tsx} (93%) create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/header_content.tsx create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/use_entity.ts diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/visualization_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/visualization_embeddable.tsx new file mode 100644 index 00000000000000..4a4974ade7217d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/visualization_embeddable.tsx @@ -0,0 +1,12 @@ +/* + * 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 from 'react'; + +export const VisualizationEmbeddable = () => ( +
+); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx index b11473e7852915..078fb4427b96c6 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx @@ -14,7 +14,7 @@ import type { import { useDeepEqualSelector } from '../../hooks/use_selector'; import { inputsSelectors } from '../../store/inputs'; import { DonutChartWrapper } from '../charts/donutchart'; -import { VisualizationEmbeddable } from './lens_embeddable_managed'; +import { VisualizationEmbeddable } from './visualization_embeddable'; import { parseVisualizationData } from './utils'; const DonutEmbeddableComponent: React.FC = (props) => { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap new file mode 100644 index 00000000000000..7bddde75530610 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getRiskScoreDonutAttributes should render 1`] = ` +Object { + "description": "", + "references": Array [], + "state": Object { + "adHocDataViews": Object { + "40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0": Object { + "allowNoIndex": false, + "fieldAttrs": Object {}, + "fieldFormats": Object {}, + "id": "40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0", + "name": "ml_host_risk_score_latest_mockSpaceId", + "runtimeFieldMap": Object {}, + "sourceFilters": Array [], + "timeFieldName": "@timestamp", + "title": "ml_host_risk_score_latest_mockSpaceId", + }, + }, + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "columnOrder": Array [ + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "columns": Object { + "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": "___records___", + }, + }, + "incompleteColumns": Object {}, + "sampling": 1, + }, + }, + }, + "textBased": Object { + "layers": Object {}, + }, + }, + "filters": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "key": "host.id", + "negate": false, + "params": Object { + "query": "123", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "host.id": "123", + }, + }, + }, + ], + "internalReferences": Array [ + Object { + "id": "40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0", + "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "type": "index-pattern", + }, + ], + "query": Object { + "language": "kql", + "query": "host.name: *", + }, + "visualization": Object { + "layers": Array [ + Object { + "categoryDisplay": "hide", + "emptySizeRatio": 0.8, + "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerType": "data", + "legendDisplay": "hide", + "legendPosition": "right", + "legendSize": "xlarge", + "metrics": Array [ + "f04a71a3-399f-4d32-9efc-8a005e989991", + ], + "nestedLegend": true, + "numberDisplay": "value", + "percentDecimals": 2, + "primaryGroups": Array [], + }, + ], + "shape": "donut", + }, + }, + "title": "host risk donut", + "visualizationType": "lnsPie", +} +`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.test.ts new file mode 100644 index 00000000000000..d306080c9d1124 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.test.ts @@ -0,0 +1,49 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { wrapper } from '../../../mocks'; + +import { useLensAttributes } from '../../../use_lens_attributes'; + +import { getRiskScoreDonutAttributes } from './risk_score_donut'; + +jest.mock('../../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + selectedPatterns: ['auditbeat-mytest-*'], + dataViewId: 'security-solution-my-test', + indicesExist: true, + }), +})); + +jest.mock('../../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + detailName: 'undefined', + pageName: 'overview', + tabName: undefined, + }, + ]), +})); + +describe('getRiskScoreDonutAttributes', () => { + it('should render', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getRiskScoreDonutAttributes, + stackByField: 'host', + extraOptions: { + spaceId: 'mockSpaceId', + }, + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts index af1ab6124a8325..c8032fd8e16057 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts @@ -7,7 +7,10 @@ import type { GetLensAttributes } from '../../../types'; -export const getRiskScoreDonutAttributes: GetLensAttributes = (stackByField, extraOptions) => ({ +export const getRiskScoreDonutAttributes: GetLensAttributes = ( + stackByField, + extraOptions = { spaceId: 'default' } +) => ({ title: `${stackByField} risk donut`, description: '', visualizationType: 'lnsPie', @@ -73,14 +76,14 @@ export const getRiskScoreDonutAttributes: GetLensAttributes = (stackByField, ext adHocDataViews: { '40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0': { id: '40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0', - title: `ml_${stackByField}_risk_score_latest_${extraOptions?.spaceId}`, + title: `ml_${stackByField}_risk_score_latest_${extraOptions.spaceId}`, timeFieldName: '@timestamp', sourceFilters: [], fieldFormats: {}, runtimeFieldMap: {}, fieldAttrs: {}, allowNoIndex: false, - name: `ml_${stackByField}_risk_score_latest_${extraOptions?.spaceId}`, + name: `ml_${stackByField}_risk_score_latest_${extraOptions.spaceId}`, }, }, }, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable_managed.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable_managed.tsx rename to x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx index ea05c7d0b9e47b..b9fdbb9642c9e9 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable_managed.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx @@ -54,6 +54,12 @@ const VisualizationEmbeddableComponent: React.FC = ); }, [dispatch, inputId, id, refetchByRestartingSession, session]); + useEffect(() => { + return () => { + dispatch(inputsActions.deleteOneQuery({ inputId, id })); + }; + }, [dispatch, id, inputId]); + return ; }; diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.test.tsx index b955e096cc295a..83ec776045a84c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.test.tsx @@ -20,8 +20,7 @@ jest.mock('../../../../common/hooks/use_experimental_features', () => { return { useIsExperimentalFeatureEnabled: jest.fn() }; }); -jest.mock('./alert_donut_embeddable'); - +jest.mock('../../../../common/components/visualization_actions/visualization_embeddable'); jest.mock('./chart_label', () => { return { ChartLabel: jest.fn((props) => ), @@ -173,7 +172,7 @@ describe('AlertsByStatus', () => { ); expect( - container.querySelector(`[data-test-subj="alert-donut-embeddable"]`) + container.querySelector(`[data-test-subj="visualization-embeddable-managed"]`) ).toBeInTheDocument(); expect((useAlertsByStatus as jest.Mock).mock.calls[0][0].skip).toBeTruthy(); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx index 5ff83782f2e597..9755f3a32bb9a5 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx @@ -274,14 +274,14 @@ export const AlertsByStatus = ({ {isChartEmbeddablesEnabled ? ( ) : ( void; + }; + onSelectSeverityFilterGroup: (newSelection: RiskSeverity[]) => void; + riskEntity: RiskScoreEntity; + selectedSeverity: RiskSeverity[]; + severityCount: SeverityCount | undefined; + toggleStatus: boolean; +}) => { + const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); + + const [goToEntityRiskTab, entityRiskTabUrl] = useMemo(() => { + const { onClick, href } = getSecuritySolutionLinkProps(entityLinkProps); + return [onClick, href]; + }, [entityLinkProps, getSecuritySolutionLinkProps]); + return toggleStatus ? ( + + + + {i18n.LEARN_MORE} + + + + + + + + {i18n.VIEW_ALL} + + + + ) : null; +}; + +export const RiskScoreHeaderContent = React.memo(RiskScoreHeaderContentComponent); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx index f0835ba32befb4..d711bdceb98e51 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx @@ -5,16 +5,9 @@ * 2.0. */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { useDispatch } from 'react-redux'; import { EnableRiskScore } from '../../../../explore/components/risk_score/enable_risk_score'; -import { getTabsOnUsersUrl } from '../../../../common/components/link_to/redirect_to_users'; -import { UsersTableType } from '../../../../explore/users/store/model'; -import { SeverityFilterGroup } from '../../../../explore/components/risk_score/severity/severity_filter_group'; -import { LinkButton, useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; -import { getTabsOnHostsUrl } from '../../../../common/components/link_to/redirect_to_hosts'; -import { HostsTableType, HostsType } from '../../../../explore/hosts/store/model'; import { getRiskScoreColumns } from './columns'; import { LastUpdatedAt } from '../../../../common/components/last_updated_at'; import { HeaderSection } from '../../../../common/components/header_section'; @@ -22,24 +15,19 @@ import { useRiskScore, useRiskScoreKpi } from '../../../../explore/containers/ri import type { RiskSeverity } from '../../../../../common/search_strategy'; import { EMPTY_SEVERITY_COUNT, RiskScoreEntity } from '../../../../../common/search_strategy'; -import { SecurityPageName } from '../../../../app/types'; -import * as i18n from './translations'; import { generateSeverityFilter } from '../../../../explore/hosts/store/helpers'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { InspectButtonContainer } from '../../../../common/components/inspect'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; -import { hostsActions } from '../../../../explore/hosts/store'; import { RiskScoreDonutChart } from '../common/risk_score_donut_chart'; import { StyledBasicTable } from '../common/styled_basic_table'; -import { RISKY_HOSTS_DOC_LINK, RISKY_USERS_DOC_LINK } from '../../../../../common/constants'; import { RiskScoreHeaderTitle } from '../../../../explore/components/risk_score/risk_score_onboarding/risk_score_header_title'; import { RiskScoresNoDataDetected } from '../../../../explore/components/risk_score/risk_score_onboarding/risk_score_no_data_detected'; import { useRefetchQueries } from '../../../../common/hooks/use_refetch_queries'; import { Loader } from '../../../../common/components/loader'; import { Panel } from '../../../../common/components/panel'; import * as commonI18n from '../common/translations'; -import { usersActions } from '../../../../explore/users/store'; import { useNavigateToTimeline } from '../../detection_response/hooks/use_navigate_to_timeline'; import type { TimeRange } from '../../../../common/store/inputs/model'; import { openAlertsFilter } from '../../detection_response/utils'; @@ -48,57 +36,15 @@ import { DonutEmbeddable } from '../../../../common/components/visualization_act import { useSpaceId } from '../../../../common/hooks/use_space_id'; import { getRiskScoreDonutAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut'; import { TOTAL_LABEL } from '../common/translations'; +import { useEntityInfo } from './use_entity'; +import { RiskScoreHeaderContent } from './header_content'; -const HOST_RISK_TABLE_QUERY_ID = 'hostRiskDashboardTable'; -const HOST_RISK_KPI_QUERY_ID = 'headerHostRiskScoreKpiQuery'; -const USER_RISK_TABLE_QUERY_ID = 'userRiskDashboardTable'; -const USER_RISK_KPI_QUERY_ID = 'headerUserRiskScoreKpiQuery'; - -// eslint-disable-next-line complexity const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskScoreEntity }) => { const { deleteQuery, setQuery, from, to } = useGlobalTime(); const [updatedAt, setUpdatedAt] = useState(Date.now()); - const dispatch = useDispatch(); const spaceId = useSpaceId(); const extraOptions = useMemo(() => ({ spaceId }), [spaceId]); - const entity = useMemo( - () => - riskEntity === RiskScoreEntity.host - ? { - docLink: RISKY_HOSTS_DOC_LINK, - linkProps: { - deepLinkId: SecurityPageName.hosts, - path: getTabsOnHostsUrl(HostsTableType.risk), - onClick: () => { - dispatch( - hostsActions.updateHostRiskScoreSeverityFilter({ - severitySelection: [], - hostsType: HostsType.page, - }) - ); - }, - }, - tableQueryId: HOST_RISK_TABLE_QUERY_ID, - kpiQueryId: HOST_RISK_KPI_QUERY_ID, - } - : { - docLink: RISKY_USERS_DOC_LINK, - linkProps: { - deepLinkId: SecurityPageName.users, - path: getTabsOnUsersUrl(UsersTableType.risk), - onClick: () => { - dispatch( - usersActions.updateUserRiskScoreSeverityFilter({ - severitySelection: [], - }) - ); - }, - }, - tableQueryId: USER_RISK_TABLE_QUERY_ID, - kpiQueryId: USER_RISK_KPI_QUERY_ID, - }, - [dispatch, riskEntity] - ); + const entity = useEntityInfo(riskEntity); const { openTimelineWithFilters } = useNavigateToTimeline(); @@ -129,7 +75,10 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc [riskEntity, openEntityInTimeline] ); const [selectedSeverity, setSelectedSeverity] = useState([]); - const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); + + const onSelectSeverityFilterGroup = useCallback((newSelection: RiskSeverity[]) => { + setSelectedSeverity(newSelection); + }, []); const severityFilter = useMemo(() => { const [filter] = generateSeverityFilter(selectedSeverity, riskEntity); @@ -199,11 +148,6 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc setUpdatedAt(Date.now()); }, [isTableLoading, isKpiLoading]); // Update the time when data loads - const [goToEntityRiskTab, entityRiskTabUrl] = useMemo(() => { - const { onClick, href } = getSecuritySolutionLinkProps(entity.linkProps); - return [onClick, href]; - }, [entity.linkProps, getSecuritySolutionLinkProps]); - const refreshPage = useRefetchQueries(); if (!isLicenseValid) { @@ -244,41 +188,20 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc toggleQuery={setToggleStatus} tooltip={commonI18n.HOST_RISK_TABLE_TOOLTIP} > - {toggleStatus && ( - - - - {i18n.LEARN_MORE} - - - - - - - - {i18n.VIEW_ALL} - - - - )} + {toggleStatus && ( - {isChartEmbeddablesEnabled && spaceId && data && data.length > 0 ? ( + {isChartEmbeddablesEnabled && spaceId && data && data.length ? ( { + const dispatch = useDispatch(); + + return riskEntity === RiskScoreEntity.host + ? { + docLink: RISKY_HOSTS_DOC_LINK, + linkProps: { + deepLinkId: SecurityPageName.hosts, + path: getTabsOnHostsUrl(HostsTableType.risk), + onClick: () => { + dispatch( + hostsActions.updateHostRiskScoreSeverityFilter({ + severitySelection: [], + hostsType: HostsType.page, + }) + ); + }, + }, + tableQueryId: HOST_RISK_TABLE_QUERY_ID, + kpiQueryId: HOST_RISK_KPI_QUERY_ID, + } + : { + docLink: RISKY_USERS_DOC_LINK, + linkProps: { + deepLinkId: SecurityPageName.users, + path: getTabsOnUsersUrl(UsersTableType.risk), + onClick: () => { + dispatch( + usersActions.updateUserRiskScoreSeverityFilter({ + severitySelection: [], + }) + ); + }, + }, + tableQueryId: USER_RISK_TABLE_QUERY_ID, + kpiQueryId: USER_RISK_KPI_QUERY_ID, + }; +}; From 56714cc6c8ae807035eddbac9223c63ef7bcd19a Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 13 Jan 2023 11:46:10 +0000 Subject: [PATCH 39/72] add unit tests --- .../common/components/charts/donutchart.tsx | 26 ++-- .../donut_embeddable.tsx | 42 ------ .../components/visualization_actions/types.ts | 2 + .../visualization_embeddable.test.tsx | 126 ++++++++++++++++++ .../visualization_embeddable.tsx | 28 +++- .../alerts_by_status/alerts_by_status.tsx | 11 +- .../alerts_by_status/types.ts | 4 - .../risk_score/header_content.test.tsx | 94 +++++++++++++ .../risk_score/header_content.tsx | 6 +- .../entity_analytics/risk_score/index.tsx | 13 +- .../risk_score/use_entity.test.ts | 51 +++++++ 11 files changed, 332 insertions(+), 71 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/header_content.test.tsx create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/use_entity.test.ts diff --git a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx index de7898b2e7296f..949ab19e4ef1d1 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx @@ -56,7 +56,7 @@ export interface DonutChartProps { export interface DonutChartWrapperProps { children?: React.ReactElement; dataExists: boolean; - label: React.ReactElement | string; + label?: React.ReactElement | string; title: React.ReactElement | string | number | null; isChartEmbeddablesEnabled?: boolean; } @@ -111,17 +111,19 @@ const DonutChartWrapperComponent: React.FC = ({ justifyContent="center" > {title} - - - - {label} - - - + {label && ( + + + + {label} + + + + )} {children} diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx deleted file mode 100644 index 078fb4427b96c6..00000000000000 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/donut_embeddable.tsx +++ /dev/null @@ -1,42 +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 from 'react'; -import { ChartLabel } from '../../../overview/components/detection_response/alerts_by_status/chart_label'; -import type { - DonutEmbeddableProps, - VisualizationAlertsByStatusResponse, -} from '../../../overview/components/detection_response/alerts_by_status/types'; -import { useDeepEqualSelector } from '../../hooks/use_selector'; -import { inputsSelectors } from '../../store/inputs'; -import { DonutChartWrapper } from '../charts/donutchart'; -import { VisualizationEmbeddable } from './visualization_embeddable'; -import { parseVisualizationData } from './utils'; - -const DonutEmbeddableComponent: React.FC = (props) => { - const { label, id, ...lensprops } = props; - const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); - const { inspect } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); - const visualizationData = inspect?.response - ? parseVisualizationData(inspect?.response) - : null; - - const dataExists = visualizationData != null && visualizationData[0]?.hits.total !== 0; - - return ( - : null} - > - - - ); -}; - -export const DonutEmbeddable = React.memo(DonutEmbeddableComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index 766b4d0a712d16..7bfc6a105584ec 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -106,5 +106,7 @@ export interface ExtraOptions { } export interface VisualizationEmbeddableProps extends LensEmbeddableComponentProps { + isDonut?: boolean; + label?: string; inputId?: InputsModelId.global | InputsModelId.timeline; } diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx new file mode 100644 index 00000000000000..c53835c0f86b95 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx @@ -0,0 +1,126 @@ +/* + * 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 from 'react'; +import type { RenderResult } from '@testing-library/react'; +import { render } from '@testing-library/react'; +import { kpiHostMetricLensAttributes } from './lens_attributes/hosts/kpi_host_metric'; +import { VisualizationEmbeddable } from './visualization_embeddable'; +import * as inputActions from '../../store/inputs/actions'; +import { InputsModelId } from '../../store/inputs/constants'; +import { + createSecuritySolutionStorageMock, + kibanaObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, + TestProviders, +} from '../../mock'; +import { createStore } from '../../store'; +import type { State } from '../../store'; +import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; +import { getRiskScoreDonutAttributes } from './lens_attributes/common/risk_scores/risk_score_donut'; +import { TOTAL_LABEL } from '../../../overview/components/entity_analytics/common/translations'; + +jest.mock('./lens_embeddable'); +jest.mock('../page/use_refetch_by_session', () => ({ + useRefetchByRestartingSession: jest.fn(), +})); + +let res: RenderResult; +const mockSearchSessionId = 'mockSearchSessionId'; +const mockSearchSessionIdDefault = 'mockSearchSessionIdDefault'; +const mockRefetchByRestartingSession = jest.fn(); +const mockSetQuery = jest.spyOn(inputActions, 'setQuery'); +const mockDeleteQuery = jest.spyOn(inputActions, 'deleteOneQuery'); +const state: State = { + ...mockGlobalState, +}; +const { storage } = createSecuritySolutionStorageMock(); +const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); +describe('VisualizationEmbeddable', () => { + describe('when isDonut = false', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ + session: { + current: { + start: jest + .fn() + .mockReturnValueOnce(mockSearchSessionId) + .mockReturnValue(mockSearchSessionIdDefault), + }, + }, + refetchByRestartingSession: mockRefetchByRestartingSession, + }); + res = render( + + + + ); + }); + + it('should render LensEmbeddable', () => { + expect(res.getByTestId('lens-embeddable')).toBeInTheDocument(); + }); + + it('should set query', () => { + expect(mockSetQuery).toHaveBeenCalledTimes(1); + expect(mockSetQuery).toHaveBeenCalledWith({ + inputId: InputsModelId.global, + id: 'testId', + searchSessionId: mockSearchSessionId, + refetch: mockRefetchByRestartingSession, + loading: false, + inspect: null, + }); + }); + + it('should delete query when unmount', () => { + res.unmount(); + expect(mockDeleteQuery).toHaveBeenCalledWith({ + inputId: InputsModelId.global, + id: 'testId', + }); + }); + }); + + describe('when isDonut = true', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ + session: { + current: { + start: jest + .fn() + .mockReturnValueOnce(mockSearchSessionId) + .mockReturnValue(mockSearchSessionIdDefault), + }, + }, + refetchByRestartingSession: mockRefetchByRestartingSession, + }); + res = render( + + + + ); + }); + + it('should render donut wrapper ', () => { + expect(res.getByTestId('donut-chart')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx index b9fdbb9642c9e9..c2807d7fd8263c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx @@ -7,7 +7,12 @@ import React, { useCallback, useEffect } from 'react'; import { useDispatch } from 'react-redux'; -import { inputsActions } from '../../store/inputs'; +import { ChartLabel } from '../../../overview/components/detection_response/alerts_by_status/chart_label'; +import type { VisualizationAlertsByStatusResponse } from '../../../overview/components/detection_response/alerts_by_status/types'; +import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { inputsActions, inputsSelectors } from '../../store/inputs'; +import { DonutChartWrapper } from '../charts/donutchart'; +import { parseVisualizationData } from './utils'; import { InputsModelId } from '../../store/inputs/constants'; import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; import { LensEmbeddable } from './lens_embeddable'; @@ -15,11 +20,17 @@ import type { EmbeddableData, VisualizationEmbeddableProps } from './types'; const VisualizationEmbeddableComponent: React.FC = (props) => { const dispatch = useDispatch(); - const { inputId = InputsModelId.global, id, onLoad, ...lensPorps } = props; + const { inputId = InputsModelId.global, id, isDonut, label, onLoad, ...lensPorps } = props; const { session, refetchByRestartingSession } = useRefetchByRestartingSession({ inputId, queryId: id, }); + const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); + const { inspect } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); + const visualizationData = inspect?.response + ? parseVisualizationData(inspect?.response) + : null; + const dataExists = visualizationData != null && visualizationData[0]?.hits.total !== 0; const onEmbeddableLoad = useCallback( ({ requests, responses, isLoading }: EmbeddableData) => { @@ -60,6 +71,19 @@ const VisualizationEmbeddableComponent: React.FC = }; }, [dispatch, id, inputId]); + if (isDonut) { + return ( + : null} + > + + + ); + } + return ; }; diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx index 9755f3a32bb9a5..4b28ad1cb6338b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx @@ -58,7 +58,7 @@ import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { useAlertsByStatusVisualizationData } from './use_alerts_by_status_visualization_data'; import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from './types'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import { DonutEmbeddable } from '../../../../common/components/visualization_actions/donut_embeddable'; +import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; import type { Status } from '../../../../../common/detection_engine/schemas/common'; import { getAlertsByStatusAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut'; @@ -219,11 +219,12 @@ export const AlertsByStatus = ({ {isChartEmbeddablesEnabled ? ( - {isChartEmbeddablesEnabled ? ( - {isChartEmbeddablesEnabled ? ( - { + const actual = jest.requireActual('../../../../common/components/links'); + return { + ...actual, + useGetSecuritySolutionLinkProps: jest.fn(), + }; +}); +const mockGetSecuritySolutionLinkProps = jest + .fn() + .mockReturnValue({ onClick: jest.fn(), href: '' }); + +describe('RiskScoreHeaderContent', () => { + let res: RenderResult; + jest.clearAllMocks(); + + (useGetSecuritySolutionLinkProps as jest.Mock).mockReturnValue(mockGetSecuritySolutionLinkProps); + beforeEach(() => { + res = render( + + ); + }); + it('should render when toggleStatus = true', () => { + expect(res.getByTestId(`user-risk-score-header-content`)).toBeInTheDocument(); + }); + + it('should render learn more button', () => { + expect(res.getByText(`Learn more`)).toBeInTheDocument(); + }); + + it('should render severity filter group', () => { + expect(res.getByTestId(`risk-filter-button`)).toBeInTheDocument(); + }); + + it('should render view all button', () => { + expect(res.getByTestId(`view-all-button`)).toBeInTheDocument(); + }); + + it('should not render if toggleStatus = false', () => { + res = render( + + ); + expect(res.getByTestId(`view-all-button`)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/header_content.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/header_content.tsx index 86591ca7dd8cbb..3ddd737486a2f5 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/header_content.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/header_content.tsx @@ -42,7 +42,11 @@ const RiskScoreHeaderContentComponent = ({ return [onClick, href]; }, [entityLinkProps, getSecuritySolutionLinkProps]); return toggleStatus ? ( - + {i18n.LEARN_MORE} diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx index d711bdceb98e51..ff3437ba79915c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx @@ -32,7 +32,7 @@ import { useNavigateToTimeline } from '../../detection_response/hooks/use_naviga import type { TimeRange } from '../../../../common/store/inputs/model'; import { openAlertsFilter } from '../../detection_response/utils'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; -import { DonutEmbeddable } from '../../../../common/components/visualization_actions/donut_embeddable'; +import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; import { useSpaceId } from '../../../../common/hooks/use_space_id'; import { getRiskScoreDonutAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut'; import { TOTAL_LABEL } from '../common/translations'; @@ -202,14 +202,15 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc {isChartEmbeddablesEnabled && spaceId && data && data.length ? ( - ) : ( diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/use_entity.test.ts b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/use_entity.test.ts new file mode 100644 index 00000000000000..967e7503c24833 --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/use_entity.test.ts @@ -0,0 +1,51 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { RiskScoreEntity } from '../../../../../common/search_strategy/security_solution/risk_score'; +import { useEntityInfo } from './use_entity'; + +jest.mock('react-redux', () => { + const actual = jest.requireActual('react-redux'); + return { + ...actual, + useDispatch: jest.fn(), + }; +}); + +describe('useEntityInfo', () => { + it('should return host entity info', () => { + const { result } = renderHook(() => useEntityInfo(RiskScoreEntity.host)); + expect(result?.current).toMatchInlineSnapshot(` + Object { + "docLink": "https://www.elastic.co/guide/en/security/current/host-risk-score.html", + "kpiQueryId": "headerHostRiskScoreKpiQuery", + "linkProps": Object { + "deepLinkId": "hosts", + "onClick": [Function], + "path": "/hostRisk", + }, + "tableQueryId": "hostRiskDashboardTable", + } + `); + }); + it('should return user entity info', () => { + const { result } = renderHook(() => useEntityInfo(RiskScoreEntity.user)); + expect(result?.current).toMatchInlineSnapshot(` + Object { + "docLink": "https://www.elastic.co/guide/en/security/current/user-risk-score.html", + "kpiQueryId": "headerUserRiskScoreKpiQuery", + "linkProps": Object { + "deepLinkId": "users", + "onClick": [Function], + "path": "/userRisk", + }, + "tableQueryId": "userRiskDashboardTable", + } + `); + }); +}); From b949db59b00374c9dd459b4e4235ad72a4a33193 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 13 Jan 2023 11:50:53 +0000 Subject: [PATCH 40/72] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../components/detection_response/alerts_by_status/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts index 2469f8b244d067..d1da9558ca9991 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts @@ -7,7 +7,6 @@ import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import type { ESQuery } from '../../../../../common/typed_json'; -import type { LensEmbeddableComponentProps } from '../../../../common/components/visualization_actions/types'; interface StatusBySeverity { doc_count_error_upper_bound: number; From 11507bedfdb67666b3ff22583762e1796cf2a3d6 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 13 Jan 2023 14:06:23 +0000 Subject: [PATCH 41/72] update configs --- .../risk_scores/__snapshots__/risk_score_donut.test.ts.snap | 2 +- .../lens_attributes/common/risk_scores/risk_score_donut.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap index 7bddde75530610..6f9d165e1e3005 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap @@ -29,7 +29,7 @@ Object { "f04a71a3-399f-4d32-9efc-8a005e989991": Object { "dataType": "number", "isBucketed": false, - "label": "Count of records", + "label": "Total", "operationType": "count", "params": Object { "emptyAsNull": true, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts index c8032fd8e16057..271a6a88d3893f 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts @@ -45,7 +45,7 @@ export const getRiskScoreDonutAttributes: GetLensAttributes = ( '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b': { columns: { 'f04a71a3-399f-4d32-9efc-8a005e989991': { - label: 'Count of records', + label: 'Total', dataType: 'number', operationType: 'count', isBucketed: false, From 11dd2b0d5e001f9b63fccb414b52fb86b67a9658 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 16 Jan 2023 12:20:40 +0000 Subject: [PATCH 42/72] an option to not apply global queries and filters --- .../__mocks__/visualization_embeddable.tsx | 4 +- .../risk_score_donut.test.ts.snap | 73 +++++-- .../common/risk_scores/risk_score_donut.ts | 196 +++++++++++------- .../visualization_actions/lens_embeddable.tsx | 2 + .../components/visualization_actions/types.ts | 1 + .../use_lens_attributes.test.tsx | 27 +++ .../use_lens_attributes.tsx | 7 +- .../alerts_by_status/alerts_by_status.tsx | 3 + .../common/risk_score_donut_chart.tsx | 2 +- .../risk_score/__mocks__/index.ts | 16 ++ .../risk_score/chart_content.test.tsx | 97 +++++++++ .../risk_score/chart_content.tsx | 61 ++++++ .../risk_score/header_content.test.tsx | 19 +- .../entity_analytics/risk_score/index.tsx | 34 +-- 14 files changed, 414 insertions(+), 128 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/__mocks__/index.ts create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/chart_content.test.tsx create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/chart_content.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/visualization_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/visualization_embeddable.tsx index 4a4974ade7217d..9979e829923fc7 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/visualization_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/__mocks__/visualization_embeddable.tsx @@ -7,6 +7,4 @@ import React from 'react'; -export const VisualizationEmbeddable = () => ( -
-); +export const VisualizationEmbeddable = () =>
; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap index 6f9d165e1e3005..2daccdaf1bae55 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/__snapshots__/risk_score_donut.test.ts.snap @@ -6,11 +6,11 @@ Object { "references": Array [], "state": Object { "adHocDataViews": Object { - "40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0": Object { + "1dd5663b-f062-43f8-8688-fc8166c2ca8e": Object { "allowNoIndex": false, "fieldAttrs": Object {}, "fieldFormats": Object {}, - "id": "40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0", + "id": "1dd5663b-f062-43f8-8688-fc8166c2ca8e", "name": "ml_host_risk_score_latest_mockSpaceId", "runtimeFieldMap": Object {}, "sourceFilters": Array [], @@ -21,15 +21,16 @@ Object { "datasourceStates": Object { "formBased": Object { "layers": Object { - "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "d594baeb-5eca-480c-8885-ba79eaf41372": Object { "columnOrder": Array [ - "f04a71a3-399f-4d32-9efc-8a005e989991", + "a2e8541a-c22f-4e43-8a12-caa33edc5de0", + "75179122-96fc-40e1-93b4-8e9310af5f06", ], "columns": Object { - "f04a71a3-399f-4d32-9efc-8a005e989991": Object { + "75179122-96fc-40e1-93b4-8e9310af5f06": Object { "dataType": "number", "isBucketed": false, - "label": "Total", + "label": "Count of records", "operationType": "count", "params": Object { "emptyAsNull": true, @@ -37,6 +38,52 @@ Object { "scale": "ratio", "sourceField": "___records___", }, + "a2e8541a-c22f-4e43-8a12-caa33edc5de0": Object { + "dataType": "string", + "isBucketed": true, + "label": "Filters", + "operationType": "filters", + "params": Object { + "filters": Array [ + Object { + "input": Object { + "language": "kuery", + "query": "host.risk.calculated_level : \\"Unknown\\"", + }, + "label": "Unknown", + }, + Object { + "input": Object { + "language": "kuery", + "query": "host.risk.calculated_level : \\"Low\\"", + }, + "label": "Low", + }, + Object { + "input": Object { + "language": "kuery", + "query": "host.risk.calculated_level : \\"Moderiate\\"", + }, + "label": "Moderiate", + }, + Object { + "input": Object { + "language": "kuery", + "query": "host.risk.calculated_level : \\"High\\"", + }, + "label": "High", + }, + Object { + "input": Object { + "language": "kuery", + "query": "host.risk.calculated_level : \\"Critical\\"", + }, + "label": "Critical", + }, + ], + }, + "scale": "ordinal", + }, }, "incompleteColumns": Object {}, "sampling": 1, @@ -68,8 +115,8 @@ Object { ], "internalReferences": Array [ Object { - "id": "40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0", - "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "id": "1dd5663b-f062-43f8-8688-fc8166c2ca8e", + "name": "indexpattern-datasource-layer-d594baeb-5eca-480c-8885-ba79eaf41372", "type": "index-pattern", }, ], @@ -82,18 +129,20 @@ Object { Object { "categoryDisplay": "hide", "emptySizeRatio": 0.8, - "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerId": "d594baeb-5eca-480c-8885-ba79eaf41372", "layerType": "data", "legendDisplay": "hide", "legendPosition": "right", - "legendSize": "xlarge", + "legendSize": "small", "metrics": Array [ - "f04a71a3-399f-4d32-9efc-8a005e989991", + "75179122-96fc-40e1-93b4-8e9310af5f06", ], "nestedLegend": true, "numberDisplay": "value", "percentDecimals": 2, - "primaryGroups": Array [], + "primaryGroups": Array [ + "a2e8541a-c22f-4e43-8a12-caa33edc5de0", + ], }, ], "shape": "donut", diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts index 271a6a88d3893f..85d71857c83313 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.ts @@ -5,87 +5,141 @@ * 2.0. */ +import uuid from 'uuid'; import type { GetLensAttributes } from '../../../types'; export const getRiskScoreDonutAttributes: GetLensAttributes = ( stackByField, extraOptions = { spaceId: 'default' } -) => ({ - title: `${stackByField} risk donut`, - description: '', - visualizationType: 'lnsPie', - state: { - visualization: { - shape: 'donut', - layers: [ - { - layerId: '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', - primaryGroups: [], - metrics: ['f04a71a3-399f-4d32-9efc-8a005e989991'], - numberDisplay: 'value', - categoryDisplay: 'hide', - legendDisplay: 'hide', - nestedLegend: true, - layerType: 'data', - legendSize: 'xlarge', - legendPosition: 'right', - percentDecimals: 2, - emptySizeRatio: 0.8, - }, - ], - }, - query: { - query: '', - language: 'kuery', - }, - filters: [], - datasourceStates: { - formBased: { - layers: { - '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b': { - columns: { - 'f04a71a3-399f-4d32-9efc-8a005e989991': { - label: 'Total', - dataType: 'number', - operationType: 'count', - isBucketed: false, - scale: 'ratio', - sourceField: '___records___', - params: { - emptyAsNull: true, +) => { + const layerId = uuid.v4(); + const internalReferenceId = uuid.v4(); + return { + title: `${stackByField} risk donut`, + description: '', + visualizationType: 'lnsPie', + state: { + visualization: { + shape: 'donut', + layers: [ + { + layerId, + primaryGroups: ['a2e8541a-c22f-4e43-8a12-caa33edc5de0'], + metrics: ['75179122-96fc-40e1-93b4-8e9310af5f06'], + numberDisplay: 'value', + categoryDisplay: 'hide', + legendDisplay: 'hide', + nestedLegend: true, + layerType: 'data', + legendSize: 'small', + legendPosition: 'right', + percentDecimals: 2, + emptySizeRatio: 0.8, + }, + ], + }, + query: { + query: '', + language: 'kuery', + }, + filters: [], + datasourceStates: { + formBased: { + layers: { + [layerId]: { + columns: { + 'a2e8541a-c22f-4e43-8a12-caa33edc5de0': { + label: 'Filters', + dataType: 'string', + operationType: 'filters', + scale: 'ordinal', + isBucketed: true, + params: { + filters: [ + { + label: 'Unknown', + input: { + query: `${stackByField}.risk.calculated_level : \"Unknown\"`, + language: 'kuery', + }, + }, + { + input: { + query: `${stackByField}.risk.calculated_level : \"Low\"`, + language: 'kuery', + }, + label: 'Low', + }, + { + input: { + query: `${stackByField}.risk.calculated_level : \"Moderiate\"`, + language: 'kuery', + }, + label: 'Moderiate', + }, + { + input: { + query: `${stackByField}.risk.calculated_level : \"High\"`, + language: 'kuery', + }, + label: 'High', + }, + { + input: { + query: `${stackByField}.risk.calculated_level : \"Critical\"`, + language: 'kuery', + }, + label: 'Critical', + }, + ], + }, + }, + '75179122-96fc-40e1-93b4-8e9310af5f06': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + params: { + emptyAsNull: true, + }, }, }, + columnOrder: [ + 'a2e8541a-c22f-4e43-8a12-caa33edc5de0', + '75179122-96fc-40e1-93b4-8e9310af5f06', + ], + sampling: 1, + incompleteColumns: {}, }, - columnOrder: ['f04a71a3-399f-4d32-9efc-8a005e989991'], - sampling: 1, - incompleteColumns: {}, }, }, + textBased: { + layers: {}, + }, }, - textBased: { - layers: {}, - }, - }, - internalReferences: [ - { - type: 'index-pattern', - id: '40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0', - name: 'indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', - }, - ], - adHocDataViews: { - '40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0': { - id: '40a4d4c8-b5da-4bb7-8961-a84ce0b73fa0', - title: `ml_${stackByField}_risk_score_latest_${extraOptions.spaceId}`, - timeFieldName: '@timestamp', - sourceFilters: [], - fieldFormats: {}, - runtimeFieldMap: {}, - fieldAttrs: {}, - allowNoIndex: false, - name: `ml_${stackByField}_risk_score_latest_${extraOptions.spaceId}`, + internalReferences: [ + { + type: 'index-pattern', + id: internalReferenceId, + name: `indexpattern-datasource-layer-${layerId}`, + }, + ], + adHocDataViews: { + [internalReferenceId]: { + id: internalReferenceId, + title: `ml_${stackByField}_risk_score_latest_${extraOptions.spaceId}`, + timeFieldName: '@timestamp', + sourceFilters: [], + fieldFormats: {}, + runtimeFieldMap: {}, + fieldAttrs: {}, + allowNoIndex: false, + name: `ml_${stackByField}_risk_score_latest_${extraOptions.spaceId}`, + }, }, }, - }, - references: [], -}); + references: [], + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 5708aee06ecad0..985a9881d330b1 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -51,6 +51,7 @@ const initVisualizationData: { const style = { height: '100%', minWidth: '100px' }; const LensEmbeddableComponent: React.FC = ({ + applyGlobalQueriesAndFilters = true, extraActions, extraOptions, getLensAttributes, @@ -72,6 +73,7 @@ const LensEmbeddableComponent: React.FC = ({ const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); const { searchSessionId } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); const attributes = useLensAttributes({ + applyGlobalQueriesAndFilters, extraOptions, getLensAttributes, lensAttributes, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index 7bfc6a105584ec..ef4830d71bd010 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -44,6 +44,7 @@ export type OnEmbeddableLoaded = (data: EmbeddableData) => void; export interface LensEmbeddableComponentProps { adHocDataViews?: string[]; + applyGlobalQueriesAndFilters?: boolean; extraActions?: Action[]; extraOptions?: ExtraOptions; getLensAttributes?: GetLensAttributes; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx index c7e0fbfa82195b..7451209120e066 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx @@ -21,6 +21,7 @@ import { filterFromSearchBar, queryFromSearchBar, wrapper } from './mocks'; import { useSourcererDataView } from '../../containers/sourcerer'; import { kpiHostMetricLensAttributes } from './lens_attributes/hosts/kpi_host_metric'; import { useRouteSpy } from '../../utils/route/use_route_spy'; +import { SecurityPageName } from '../../../app/types'; jest.mock('../../containers/sourcerer'); jest.mock('../../utils/route/use_route_spy', () => ({ @@ -126,6 +127,32 @@ describe('useLensAttributes', () => { ]); }); + it('should not apply global queries and filters - applyGlobalQueriesAndFilters = false', () => { + (useRouteSpy as jest.Mock).mockReturnValue([ + { + detailName: undefined, + pageName: SecurityPageName.entityAnalytics, + tabName: undefined, + }, + ]); + const { result } = renderHook( + () => + useLensAttributes({ + applyGlobalQueriesAndFilters: false, + getLensAttributes: getExternalAlertLensAttributes, + stackByField: 'event.dataset', + }), + { wrapper } + ); + + expect(result?.current?.state.query.query).toEqual(''); + + expect(result?.current?.state.filters).toEqual([ + ...getExternalAlertLensAttributes().state.filters, + ...getIndexFilters(['auditbeat-*']), + ]); + }); + it('should add data view id to references', () => { const { result } = renderHook( () => diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index 6ec8370cd168f9..1976a743e5fa16 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -24,6 +24,7 @@ import { } from './utils'; export const useLensAttributes = ({ + applyGlobalQueriesAndFilters = true, extraOptions, getLensAttributes, lensAttributes, @@ -31,6 +32,7 @@ export const useLensAttributes = ({ stackByField, title, }: { + applyGlobalQueriesAndFilters?: boolean; extraOptions?: ExtraOptions; getLensAttributes?: GetLensAttributes; lensAttributes?: LensAttributes | null; @@ -97,10 +99,10 @@ export const useLensAttributes = ({ ...(title != null ? { title } : {}), state: { ...attrs.state, - query, + ...(applyGlobalQueriesAndFilters ? { query } : {}), filters: [ ...attrs.state.filters, - ...filters, + ...(applyGlobalQueriesAndFilters ? filters : []), ...pageFilters, ...tabsFilters, ...indexFilters, @@ -112,6 +114,7 @@ export const useLensAttributes = ({ })), } as LensAttributes; }, [ + applyGlobalQueriesAndFilters, attrs, dataViewId, filters, diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx index 4b28ad1cb6338b..a23bfa31c9d105 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.tsx @@ -220,6 +220,7 @@ export const AlertsByStatus = ({ {isChartEmbeddablesEnabled ? ( {isChartEmbeddablesEnabled ? ( {isChartEmbeddablesEnabled ? ( + {legendItems.length > 0 && } diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/__mocks__/index.ts b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/__mocks__/index.ts new file mode 100644 index 00000000000000..bf863d0011ad38 --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/__mocks__/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { RiskSeverity } from '../../../../../../common/search_strategy/security_solution'; + +export const mockSeverityCount = { + [RiskSeverity.unknown]: 1, + [RiskSeverity.low]: 2, + [RiskSeverity.moderate]: 3, + [RiskSeverity.high]: 4, + [RiskSeverity.critical]: 5, +}; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/chart_content.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/chart_content.test.tsx new file mode 100644 index 00000000000000..cd2183cc9611fb --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/chart_content.test.tsx @@ -0,0 +1,97 @@ +/* + * 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 { render } from '@testing-library/react'; +import React from 'react'; +import { RiskScoreEntity } from '../../../../../common/search_strategy'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { useSpaceId } from '../../../../common/hooks/use_space_id'; +import { TestProviders } from '../../../../common/mock'; +import { ChartContent } from './chart_content'; +import { mockSeverityCount } from './__mocks__'; + +jest.mock('../../../../common/components/visualization_actions/visualization_embeddable'); +jest.mock('../../../../common/hooks/use_experimental_features', () => ({ + useIsExperimentalFeatureEnabled: jest.fn(), +})); +jest.mock('../../../../common/hooks/use_space_id', () => ({ + useSpaceId: jest.fn(), +})); +describe('ChartContent', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); + (useSpaceId as jest.Mock).mockReturnValue('default'); + }); + it('renders VisualizationEmbeddable when isChartEmbeddablesEnabled = true and dataExists = true', () => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + + const { getByTestId } = render( + + ); + + expect(getByTestId('visualization-embeddable')).toBeInTheDocument(); + }); + + it('should not render VisualizationEmbeddable when dataExists = false', () => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('visualization-embeddable')).not.toBeInTheDocument(); + }); + + it('should not render VisualizationEmbeddable when spaceId = undefined', () => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + (useSpaceId as jest.Mock).mockReturnValue(undefined); + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('visualization-embeddable')).not.toBeInTheDocument(); + }); + + it('renders RiskScoreDonutChart when isChartEmbeddablesEnabled = false', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('risk-score-donut-chart')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/chart_content.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/chart_content.tsx new file mode 100644 index 00000000000000..40acfe37f59a7e --- /dev/null +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/chart_content.tsx @@ -0,0 +1,61 @@ +/* + * 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, { useMemo } from 'react'; +import type { RiskScoreEntity } from '../../../../../common/search_strategy'; +import { EMPTY_SEVERITY_COUNT } from '../../../../../common/search_strategy'; +import { getRiskScoreDonutAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut'; +import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { useSpaceId } from '../../../../common/hooks/use_space_id'; +import type { SeverityCount } from '../../../../explore/components/risk_score/severity/types'; +import { RiskScoreDonutChart } from '../common/risk_score_donut_chart'; +import { TOTAL_LABEL } from '../common/translations'; + +const ChartContentComponent = ({ + dataExists, + kpiQueryId, + riskEntity, + severityCount, + timerange, +}: { + dataExists?: boolean; + kpiQueryId: string; + riskEntity: RiskScoreEntity; + severityCount: SeverityCount | undefined; + timerange: { + from: string; + to: string; + }; +}) => { + const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); + const spaceId = useSpaceId(); + const extraOptions = useMemo(() => ({ spaceId }), [spaceId]); + + return ( + <> + {isChartEmbeddablesEnabled && spaceId && dataExists && ( + + )} + {!isChartEmbeddablesEnabled && ( + + )} + + ); +}; + +export const ChartContent = React.memo(ChartContentComponent); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/header_content.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/header_content.test.tsx index 20a750ee1691ea..262bf4792288fe 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/header_content.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/header_content.test.tsx @@ -8,9 +8,10 @@ import type { RenderResult } from '@testing-library/react'; import { render } from '@testing-library/react'; import React from 'react'; import { SecurityPageName } from '../../../../../common/constants'; -import { RiskScoreEntity, RiskSeverity } from '../../../../../common/search_strategy'; +import { RiskScoreEntity } from '../../../../../common/search_strategy'; import { useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; import { RiskScoreHeaderContent } from './header_content'; +import { mockSeverityCount } from './__mocks__'; jest.mock('../../../../common/components/links', () => { const actual = jest.requireActual('../../../../common/components/links'); @@ -40,13 +41,7 @@ describe('RiskScoreHeaderContent', () => { onSelectSeverityFilterGroup={jest.fn()} riskEntity={RiskScoreEntity.user} selectedSeverity={[]} - severityCount={{ - [RiskSeverity.unknown]: 1, - [RiskSeverity.low]: 2, - [RiskSeverity.moderate]: 3, - [RiskSeverity.high]: 4, - [RiskSeverity.critical]: 5, - }} + severityCount={mockSeverityCount} toggleStatus={true} /> ); @@ -79,13 +74,7 @@ describe('RiskScoreHeaderContent', () => { onSelectSeverityFilterGroup={jest.fn()} riskEntity={RiskScoreEntity.user} selectedSeverity={[]} - severityCount={{ - [RiskSeverity.unknown]: 1, - [RiskSeverity.low]: 2, - [RiskSeverity.moderate]: 3, - [RiskSeverity.high]: 4, - [RiskSeverity.critical]: 5, - }} + severityCount={mockSeverityCount} toggleStatus={false} /> ); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx index ff3437ba79915c..9f6e0180834592 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx @@ -14,13 +14,12 @@ import { HeaderSection } from '../../../../common/components/header_section'; import { useRiskScore, useRiskScoreKpi } from '../../../../explore/containers/risk_score'; import type { RiskSeverity } from '../../../../../common/search_strategy'; -import { EMPTY_SEVERITY_COUNT, RiskScoreEntity } from '../../../../../common/search_strategy'; +import { RiskScoreEntity } from '../../../../../common/search_strategy'; import { generateSeverityFilter } from '../../../../explore/hosts/store/helpers'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { InspectButtonContainer } from '../../../../common/components/inspect'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; -import { RiskScoreDonutChart } from '../common/risk_score_donut_chart'; import { StyledBasicTable } from '../common/styled_basic_table'; import { RiskScoreHeaderTitle } from '../../../../explore/components/risk_score/risk_score_onboarding/risk_score_header_title'; import { RiskScoresNoDataDetected } from '../../../../explore/components/risk_score/risk_score_onboarding/risk_score_no_data_detected'; @@ -31,19 +30,14 @@ import * as commonI18n from '../common/translations'; import { useNavigateToTimeline } from '../../detection_response/hooks/use_navigate_to_timeline'; import type { TimeRange } from '../../../../common/store/inputs/model'; import { openAlertsFilter } from '../../detection_response/utils'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; -import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; -import { useSpaceId } from '../../../../common/hooks/use_space_id'; -import { getRiskScoreDonutAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut'; -import { TOTAL_LABEL } from '../common/translations'; + import { useEntityInfo } from './use_entity'; import { RiskScoreHeaderContent } from './header_content'; +import { ChartContent } from './chart_content'; const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskScoreEntity }) => { const { deleteQuery, setQuery, from, to } = useGlobalTime(); const [updatedAt, setUpdatedAt] = useState(Date.now()); - const spaceId = useSpaceId(); - const extraOptions = useMemo(() => ({ spaceId }), [spaceId]); const entity = useEntityInfo(riskEntity); const { openTimelineWithFilters } = useNavigateToTimeline(); @@ -93,7 +87,6 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc }), [from, to] ); - const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const { severityCount, @@ -201,20 +194,13 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc {toggleStatus && ( - {isChartEmbeddablesEnabled && spaceId && data && data.length ? ( - - ) : ( - - )} + 0} + kpiQueryId={entity.kpiQueryId} + riskEntity={riskEntity} + severityCount={severityCount} + timerange={timerange} + /> Date: Mon, 16 Jan 2023 15:04:53 +0000 Subject: [PATCH 43/72] update snapshot --- .../common/risk_scores/risk_score_donut.test.ts | 7 +++++++ .../alerts_by_status/alerts_by_status.test.tsx | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.test.ts index d306080c9d1124..b7a30e1c029791 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/risk_scores/risk_score_donut.test.ts @@ -30,6 +30,13 @@ jest.mock('../../../../../utils/route/use_route_spy', () => ({ ]), })); +jest.mock('uuid', () => ({ + v4: jest + .fn() + .mockReturnValueOnce('d594baeb-5eca-480c-8885-ba79eaf41372') + .mockReturnValue('1dd5663b-f062-43f8-8688-fc8166c2ca8e'), +})); + describe('getRiskScoreDonutAttributes', () => { it('should render', () => { const { result } = renderHook( diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.test.tsx index 83ec776045a84c..40a9fab612ad69 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alerts_by_status.test.tsx @@ -172,7 +172,7 @@ describe('AlertsByStatus', () => { ); expect( - container.querySelector(`[data-test-subj="visualization-embeddable-managed"]`) + container.querySelector(`[data-test-subj="visualization-embeddable"]`) ).toBeInTheDocument(); expect((useAlertsByStatus as jest.Mock).mock.calls[0][0].skip).toBeTruthy(); }); From a5712f76ab449162bd950bb795d541536faa1e55 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 16 Jan 2023 21:18:17 +0000 Subject: [PATCH 44/72] sync up with main --- .../components/alerts_treemap_panel/index.tsx | 16 ++--- .../components/matrix_histogram/index.tsx | 17 ++---- .../alerts_kpis/alerts_count_panel/index.tsx | 16 ++--- .../alerts_histogram_panel/index.tsx | 31 ++-------- .../severity_donut/severity_level_chart.tsx | 33 ++++++++--- .../rules/rule_preview/preview_histogram.tsx | 29 +++------ .../alert_by_severity_table_embeddable.tsx | 59 ------------------- .../alerts_by_status/types.ts | 1 - 8 files changed, 50 insertions(+), 152 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/alert_by_severity_table_embeddable.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index 9f558951611f1c..b38d09c2f7706e 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -25,12 +25,10 @@ import { HeaderSection } from '../header_section'; import { InspectButtonContainer } from '../inspect'; import { DEFAULT_STACK_BY_FIELD0_SIZE, getAlertsRiskQuery } from '../alerts_treemap/query'; import type { AlertsTreeMapAggregation } from '../alerts_treemap/types'; -import { InputsModelId } from '../../store/inputs/constants'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; -import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; -import { LensEmbeddable } from '../visualization_actions/lens_embeddable'; import { getAlertsTreemapLensAttributes as getLensAttributes } from '../visualization_actions/lens_attributes/common/alerts/alerts_treemap'; import { SourcererScopeName } from '../../store/sourcerer/model'; +import { VisualizationEmbeddable } from '../visualization_actions/visualization_embeddable'; const DEFAULT_HEIGHT = DEFAULT_MIN_CHART_HEIGHT + 134; // px @@ -95,10 +93,7 @@ const AlertsTreemapPanelComponent: React.FC = ({ const uniqueQueryId = useMemo(() => `${ALERTS_TREEMAP_ID}-${uuid.v4()}`, []); const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const timerange = useMemo(() => ({ from, to }), [from, to]); - const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ - inputId: InputsModelId.global, - queryId: uniqueQueryId, - }); + const extraVisualizationOptions = useMemo( () => ({ breakdownField: stackByField1, @@ -169,10 +164,9 @@ const AlertsTreemapPanelComponent: React.FC = ({ useInspectButton({ deleteQuery, loading: isLoadingAlerts, - refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + refetch, request, response, - searchSessionId, setQuery, uniqueQueryId, }); @@ -220,12 +214,12 @@ const AlertsTreemapPanelComponent: React.FC = ({ {isPanelExpanded ? ( isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( - & { @@ -169,11 +168,6 @@ export const MatrixHistogramComponent: React.FC = ); const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); - const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ - inputId: InputsModelId.global, - queryId: id, - skip: !isChartEmbeddablesEnabled, - }); const matrixHistogramRequest = { endDate, @@ -218,8 +212,7 @@ export const MatrixHistogramComponent: React.FC = id, inspect, loading, - refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, - searchSessionId, + refetch, }); } @@ -235,8 +228,6 @@ export const MatrixHistogramComponent: React.FC = isInitialLoading, loading, refetch, - refetchByRestartingSession, - searchSessionId, setIsInitialLoading, setQuery, ]); @@ -308,11 +299,11 @@ export const MatrixHistogramComponent: React.FC = {toggleStatus ? ( isChartEmbeddablesEnabled && (getLensAttributes || lensAttributes) && timerange ? ( - ( const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const timerange = useMemo(() => ({ from, to }), [from, to]); - const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ - inputId: InputsModelId.global, - queryId: uniqueQueryId, - }); + const extraVisualizationOptions = useMemo( () => ({ breakdownField: stackByField1, @@ -176,10 +171,9 @@ export const AlertsCountPanel = memo( useInspectButton({ deleteQuery, loading: isLoadingAlerts, - refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + refetch, request, response, - searchSessionId, setQuery, uniqueQueryId, }); @@ -222,13 +216,13 @@ export const AlertsCountPanel = memo( {toggleStatus ? ( isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( - ( const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const timerange = useMemo(() => ({ from, to }), [from, to]); - const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ - inputId: InputsModelId.global, - queryId: uniqueQueryId, - }); + const extraVisualizationOptions = useMemo( () => ({ filters, @@ -286,27 +280,13 @@ export const AlertsHistogramPanel = memo( useInspectButton({ deleteQuery, loading: isLoadingAlerts, - refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, + refetch, request, response, - searchSessionId, setQuery, uniqueQueryId, }); - const onEmbeddableLoad = useCallback( - ({ requests, responses, isLoading }: EmbeddableData) => { - setQuery({ - id: uniqueQueryId, - searchSessionId, - refetch: refetchByRestartingSession, - loading: isLoading, - inspect: { dsl: requests, response: responses }, - }); - }, - [refetchByRestartingSession, searchSessionId, setQuery, uniqueQueryId] - ); - useEffect(() => { setTotalAlertsObj( alertsData?.hits.total ?? { @@ -442,18 +422,17 @@ export const AlertsHistogramPanel = memo( {toggleStatus ? ( isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( - ) : isInitialLoading ? ( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx index 16d843c1bfa23c..8e8cd2190f43c7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx @@ -21,9 +21,11 @@ import { HeaderSection } from '../../../../../common/components/header_section'; import { InspectButtonContainer } from '../../../../../common/components/inspect'; import { getSeverityTableColumns } from '../columns'; import { getSeverityColor } from '../helpers'; -import { AlertDonutEmbeddable } from '../../../../../overview/components/detection_response/alerts_by_status/alert_donut_embeddable'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; -import { AlertBySeverityTableEmbeddable } from '../../../../../overview/components/detection_response/alerts_by_status/alert_by_severity_table_embeddable'; +import { getAlertsByStatusAttributes } from '../../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut'; +import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; +import { VisualizationEmbeddable } from '../../../../../common/components/visualization_actions/visualization_embeddable'; +import { getAlertsBySeverityTableAttributes } from '../../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table'; const DONUT_HEIGHT = 150; @@ -83,7 +85,7 @@ export const SeverityLevelChart: React.FC = ({ }, [addFilter] ); - + const extraOptions = useMemo(() => ({ filters }), [filters]); return ( @@ -100,10 +102,15 @@ export const SeverityLevelChart: React.FC = ({ {isChartEmbeddablesEnabled ? ( - ) : ( = ({ {isChartEmbeddablesEnabled ? ( - ) : ( ({ from: startDate, to: endDate }), [startDate, endDate]); - const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ - inputId: InputsModelId.global, - queryId: `${ID}-${previewId}`, - }); + const extraVisualizationOptions = useMemo( () => ({ ruleId: previewId, @@ -139,21 +134,10 @@ export const PreviewHistogram = ({ id: `${ID}-${previewId}`, inspect, loading: isLoading, - refetch: isChartEmbeddablesEnabled ? refetchByRestartingSession : refetch, - searchSessionId, + refetch, }); } - }, [ - setQuery, - inspect, - isLoading, - isInitializing, - refetch, - previewId, - isChartEmbeddablesEnabled, - refetchByRestartingSession, - searchSessionId, - ]); + }, [setQuery, inspect, isLoading, isInitializing, refetch, previewId, isChartEmbeddablesEnabled]); const barConfig = useMemo( (): ChartSeriesConfigs => getHistogramConfig(endDate, startDate, !isEqlRule), @@ -206,11 +190,12 @@ export const PreviewHistogram = ({ {isLoading ? ( ) : isChartEmbeddablesEnabled ? ( - = ({ - filters, - timerange, -}) => { - const dispatch = useDispatch(); - const queryId = `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-table`; - const { searchSessionId, refetchByRestartingSession } = useRefetchByRestartingSession({ - inputId: InputsModelId.global, - queryId, - }); - - const extraOptions = useMemo(() => ({ filters }), [filters]); - - useEffect(() => { - dispatch( - inputsActions.setQuery({ - inputId: InputsModelId.global, - id: queryId, - searchSessionId, - refetch: refetchByRestartingSession, - loading: false, - inspect: null, - }) - ); - }, [dispatch, queryId, refetchByRestartingSession, searchSessionId]); - - return ( - - ); -}; - -export const AlertBySeverityTableEmbeddable = React.memo(AlertBySeverityTableEmbeddableComponent); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts index fca816c928c9ab..d1da9558ca9991 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/alerts_by_status/types.ts @@ -6,7 +6,6 @@ */ import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { Filter } from '@kbn/es-query'; import type { ESQuery } from '../../../../../common/typed_json'; interface StatusBySeverity { From 0019feabc16989246c03803e27fe9c61c5119657 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 16 Jan 2023 21:46:17 +0000 Subject: [PATCH 45/72] fix unit tests --- .../alerts_treemap_panel/index.test.tsx | 32 ------------------ .../page/__mocks__/use_refetch_by_session.tsx | 18 ++++++++++ .../alerts_count_panel/index.test.tsx | 33 +------------------ .../alerts_histogram_panel/index.test.tsx | 33 +------------------ .../detection_engine.test.tsx | 7 +--- 5 files changed, 21 insertions(+), 102 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/page/__mocks__/use_refetch_by_session.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx index d89784c3aead82..0988fe81eeaa8e 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx @@ -24,8 +24,6 @@ import type { Props } from '.'; import { AlertsTreemapPanel } from '.'; import { mockAlertSearchResponse } from '../alerts_treemap/lib/mocks/mock_alert_search_response'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; -import { useInspectButton } from '../../../detections/components/alerts_kpis/common/hooks'; -import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; const from = '2022-07-28T08:20:18.966Z'; const to = '2022-07-28T08:20:18.966Z'; @@ -60,12 +58,6 @@ jest.mock('../../hooks/use_experimental_features'); jest.mock('../page/use_refetch_by_session'); jest.mock('../visualization_actions/lens_embeddable'); -jest.mock('../page/use_refetch_by_session', () => ({ - useRefetchByRestartingSession: jest.fn().mockReturnValue({ - searchSessionId: 'mockSearchSessionId', - refetchByRestartingSession: jest.fn(), - }), -})); jest.mock('../../../detections/components/alerts_kpis/common/hooks', () => ({ useInspectButton: jest.fn(), useStackByFields: jest.fn(), @@ -330,18 +322,11 @@ describe('AlertsTreemapPanel', () => { }); describe('when isChartEmbeddablesEnabled = true', () => { - const mockSearchSessionId = 'mockSearchSessionId'; - const mockRefetchByRestartingSession = jest.fn(); const mockRefetch = () => {}; beforeEach(() => { jest.clearAllMocks(); - (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ - searchSessionId: mockSearchSessionId, - refetchByRestartingSession: mockRefetchByRestartingSession, - }); - (useLocation as jest.Mock).mockReturnValue([ { pageName: SecurityPageName.alerts, detailName: undefined }, ]); @@ -358,23 +343,6 @@ describe('when isChartEmbeddablesEnabled = true', () => { (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); }); - it('refetch data by refetchByRestartingSession', async () => { - render( - - - - ); - - await waitFor(() => { - expect((useInspectButton as jest.Mock).mock.calls[0][0].refetch).toEqual( - mockRefetchByRestartingSession - ); - expect((useInspectButton as jest.Mock).mock.calls[0][0].searchSessionId).toEqual( - mockSearchSessionId - ); - }); - }); - it('renders LensEmbeddable', async () => { render( diff --git a/x-pack/plugins/security_solution/public/common/components/page/__mocks__/use_refetch_by_session.tsx b/x-pack/plugins/security_solution/public/common/components/page/__mocks__/use_refetch_by_session.tsx new file mode 100644 index 00000000000000..3d4bafab8614a3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/page/__mocks__/use_refetch_by_session.tsx @@ -0,0 +1,18 @@ +/* + * 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 const useRefetchByRestartingSession = jest.fn().mockReturnValue({ + session: { + current: { + start: jest + .fn() + .mockReturnValueOnce('mockSearchSessionId') + .mockReturnValue('mockSearchSessionIdDefault'), + }, + }, + refetchByRestartingSession: jest.fn(), +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx index ee4f6810ffac07..cc2e5ca8c78d01 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx @@ -18,9 +18,7 @@ import { DEFAULT_STACK_BY_FIELD, DEFAULT_STACK_BY_FIELD1 } from '../common/confi import { TestProviders } from '../../../../common/mock'; import { ChartContextMenu } from '../../../pages/detection_engine/chart_panels/chart_context_menu'; import { TABLE } from '../../../pages/detection_engine/chart_panels/chart_select/translations'; -import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; -import { useInspectButton } from '../common/hooks'; const from = '2022-07-28T08:20:18.966Z'; const to = '2022-07-28T08:20:18.966Z'; @@ -58,12 +56,7 @@ jest.mock('../../../containers/detection_engine/alerts/use_query', () => { jest.mock('../../../../common/hooks/use_experimental_features'); jest.mock('../../../../common/components/page/use_refetch_by_session'); jest.mock('../../../../common/components/visualization_actions/lens_embeddable'); -jest.mock('../../../../common/components/page/use_refetch_by_session', () => ({ - useRefetchByRestartingSession: jest.fn().mockReturnValue({ - searchSessionId: 'mockSearchSessionId', - refetchByRestartingSession: jest.fn(), - }), -})); +jest.mock('../../../../common/components/page/use_refetch_by_session'); jest.mock('../common/hooks', () => ({ useInspectButton: jest.fn(), useStackByFields: jest.fn(), @@ -221,38 +214,14 @@ describe('AlertsCountPanel', () => { }); describe('when isChartEmbeddablesEnabled = true', () => { - const mockSearchSessionId = 'mockSearchSessionId'; - const mockRefetchByRestartingSession = jest.fn(); - beforeEach(() => { jest.clearAllMocks(); mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); - (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ - searchSessionId: mockSearchSessionId, - refetchByRestartingSession: mockRefetchByRestartingSession, - }); - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); }); - it('refetch data by refetchByRestartingSession', async () => { - await act(async () => { - mount( - - - - ); - expect((useInspectButton as jest.Mock).mock.calls[0][0].refetch).toEqual( - mockRefetchByRestartingSession - ); - expect((useInspectButton as jest.Mock).mock.calls[0][0].searchSessionId).toEqual( - mockSearchSessionId - ); - }); - }); - it('renders LensEmbeddable', async () => { await act(async () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx index ccbf87b4e9bfbc..a90b86dc4f4cd9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx @@ -21,8 +21,6 @@ import * as helpers from './helpers'; import { mockAlertSearchResponse } from './mock_data'; import { ChartContextMenu } from '../../../pages/detection_engine/chart_panels/chart_context_menu'; import { AlertsHistogramPanel, LEGEND_WITH_COUNTS_WIDTH } from '.'; -import { useInspectButton } from '../common/hooks'; -import { useRefetchByRestartingSession } from '../../../../common/components/page/use_refetch_by_session'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; jest.mock('../../../../common/containers/query_toggle'); @@ -102,12 +100,7 @@ jest.mock('../../../../common/hooks/use_experimental_features'); jest.mock('../../../../common/components/page/use_refetch_by_session'); jest.mock('../../../../common/components/visualization_actions/lens_embeddable'); -jest.mock('../../../../common/components/page/use_refetch_by_session', () => ({ - useRefetchByRestartingSession: jest.fn().mockReturnValue({ - searchSessionId: 'mockSearchSessionId', - refetchByRestartingSession: jest.fn(), - }), -})); +jest.mock('../../../../common/components/page/use_refetch_by_session'); jest.mock('../common/hooks', () => { const actual = jest.requireActual('../common/hooks'); return { @@ -722,38 +715,14 @@ describe('AlertsHistogramPanel', () => { }); describe('when isChartEmbeddablesEnabled = true', () => { - const mockSearchSessionId = 'mockSearchSessionId'; - const mockRefetchByRestartingSession = jest.fn(); - beforeEach(() => { jest.clearAllMocks(); mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); - (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ - searchSessionId: mockSearchSessionId, - refetchByRestartingSession: mockRefetchByRestartingSession, - }); - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); }); - it('refetch data by refetchByRestartingSession', async () => { - await act(async () => { - mount( - - - - ); - expect((useInspectButton as jest.Mock).mock.calls[0][0].refetch).toEqual( - mockRefetchByRestartingSession - ); - expect((useInspectButton as jest.Mock).mock.calls[0][0].searchSessionId).toEqual( - mockSearchSessionId - ); - }); - }); - it('renders LensEmbeddable', async () => { await act(async () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index 03f600d686a747..812c35acd4e9fa 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -124,12 +124,7 @@ jest.mock('../../components/alerts_table/timeline_actions/use_bulk_add_to_case_a })); jest.mock('../../../common/components/visualization_actions/lens_embeddable'); -jest.mock('../../../common/components/page/use_refetch_by_session', () => ({ - useRefetchByRestartingSession: jest.fn().mockReturnValue({ - searchSessionId: 'mockSearchSessionId', - refetchByRestartingSession: jest.fn(), - }), -})); +jest.mock('../../../common/components/page/use_refetch_by_session'); describe('DetectionEnginePageComponent', () => { beforeAll(() => { From 29545fd67fdb8e090f39d8055d42af1c337616d1 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 17 Jan 2023 09:43:29 +0000 Subject: [PATCH 46/72] unit tests --- .../public/actions/show_top_n/show_top_n_component.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security_solution/public/actions/show_top_n/show_top_n_component.test.tsx b/x-pack/plugins/security_solution/public/actions/show_top_n/show_top_n_component.test.tsx index 68af0eff06c0a1..7ae45fbdef958f 100644 --- a/x-pack/plugins/security_solution/public/actions/show_top_n/show_top_n_component.test.tsx +++ b/x-pack/plugins/security_solution/public/actions/show_top_n/show_top_n_component.test.tsx @@ -20,6 +20,7 @@ jest.mock('react-router-dom', () => { useLocation: jest.fn().mockReturnValue({ pathname: '/test' }), }; }); +jest.mock('../../common/components/visualization_actions'); const casesService = { ui: { getCasesContext: () => mockCasesContext }, From 374f6520874186a5be450a7eee1acc06e8004332 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 24 Jan 2023 15:20:48 +0000 Subject: [PATCH 47/72] review --- .../alerts_treemap_panel/index.test.tsx | 8 +- .../components/matrix_histogram/index.tsx | 4 +- .../alerts_by_severity_table.test.ts.snap | 161 ++++++++++ .../alerts_by_status_donut.test.ts.snap | 166 +++++++++++ .../alerts_histogram.test.ts.snap | 2 - .../__snapshots__/rule_preview.test.ts.snap | 173 +++++++++++ .../alerts/alerts_by_severity_table.test.ts | 63 ++++ .../common/alerts/alerts_by_severity_table.ts | 213 +++++++------- .../alerts/alerts_by_status_donut.test.ts | 63 ++++ .../common/alerts/alerts_by_status_donut.ts | 18 +- .../common/alerts/alerts_histogram.test.ts | 4 + .../common/alerts/alerts_histogram.ts | 19 +- .../common/alerts/alerts_table.test.ts | 4 + .../common/alerts/alerts_table.ts | 228 ++++++++------- .../common/alerts/alerts_treemap.test.ts | 4 + .../common/alerts/alerts_treemap.ts | 226 ++++++++------- .../common/alerts/rule_preview.test.ts | 73 +++++ .../common/alerts/rule_preview.ts | 274 +++++++++--------- .../lens_attributes/common/event.test.ts | 4 + .../lens_attributes/common/events.ts | 19 +- .../visualization_actions/lens_embeddable.tsx | 1 - .../visualization_actions/mocks.tsx | 47 +++ .../alerts_kpis/alerts_count_panel/index.tsx | 4 +- .../alerts_histogram_panel/index.tsx | 11 +- .../severity_donut/severity_level_chart.tsx | 6 +- .../rules/rule_preview/preview_histogram.tsx | 20 +- 26 files changed, 1303 insertions(+), 512 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_by_severity_table.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_by_status_donut.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/rule_preview.test.ts.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx index 0988fe81eeaa8e..d843866233dd60 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx @@ -350,7 +350,7 @@ describe('when isChartEmbeddablesEnabled = true', () => { ); - await waitFor(() => expect(screen.getByTestId('lens-embeddable')).toBeInTheDocument()); + expect(screen.getByTestId('lens-embeddable')).toBeInTheDocument(); }); it('should skip calling getAlertsRiskQuery', async () => { @@ -360,6 +360,10 @@ describe('when isChartEmbeddablesEnabled = true', () => { ); - await waitFor(() => expect((useQueryAlerts as jest.Mock).mock.calls[0][0].skip).toBeTruthy()); + expect(useQueryAlerts).toHaveBeenCalledWith( + expect.objectContaining({ + skip: true, + }) + ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index dea84ffcc861c6..5287855a8778d7 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -70,7 +70,7 @@ const HistogramPanel = styled(Panel)<{ height?: number }>` ${({ height }) => (height != null ? `min-height: ${height}px;` : '')} `; -const ChartHeight = '150px'; +const CHART_HEIGHT = '150px'; export const MatrixHistogramComponent: React.FC = ({ chartHeight, @@ -302,7 +302,7 @@ export const MatrixHistogramComponent: React.FC = ({ + v4: jest.fn().mockReturnValue('b9b43606-7ff7-46ae-a47c-85bed80fab9a'), +})); + +jest.mock('../../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + dataViewId: 'security-solution-my-test', + indicesExist: true, + selectedPatterns: ['signal-index'], + }), +})); + +jest.mock('../../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + pageName: 'alerts', + }, + ]), +})); + +describe('getAlertsBySeverityTableAttributes', () => { + it('should render without extra options', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getAlertsBySeverityTableAttributes, + stackByField: 'kibana.alert.severity', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - filters', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { + filters: mockExtraFilter, + }, + getLensAttributes: getAlertsBySeverityTableAttributes, + stackByField: 'kibana.alert.severity', + }), + { wrapper } + ); + + expect(result?.current?.state.filters).toEqual(expect.arrayContaining(mockExtraFilter)); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts index 6ad552c856df89..a7f8b490f48b91 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts @@ -5,123 +5,126 @@ * 2.0. */ +import { v4 as uuidv4 } from 'uuid'; import type { GetLensAttributes } from '../../../types'; - export const getAlertsBySeverityTableAttributes: GetLensAttributes = ( - stackByField = 'kibana.alert.workflow_status', + stackByField = 'kibana.alert.severity', extraOptions -) => ({ - title: 'alerts-table', - description: '', - visualizationType: 'lnsDatatable', - state: { - visualization: { - layerId: '51ed355e-6e23-4038-a417-f653a1160370', - layerType: 'data', - columns: [ - { - columnId: 'a9b43606-7ff7-46ae-a47c-85bed80fab9a', - }, - { - columnId: '21cc4a49-3780-4b1a-be28-f02fa5303d24', - }, - ], - headerRowHeight: 'custom', - headerRowHeightLines: 0.6, - rowHeight: 'custom', - rowHeightLines: 0.8, - }, - query: { - query: '', - language: 'kuery', - }, - filters: extraOptions?.filters ? extraOptions.filters : [], - datasourceStates: { - formBased: { - layers: { - '51ed355e-6e23-4038-a417-f653a1160370': { - columns: { - 'a9b43606-7ff7-46ae-a47c-85bed80fab9a': { - label: 'Filters', - dataType: 'string', - operationType: 'filters', - scale: 'ordinal', - isBucketed: true, - params: { - filters: [ - { - input: { - query: 'kibana.alert.severity: "critical"', - language: 'kuery', +) => { + const layerId = uuidv4(); + return { + title: 'Alerts', + description: '', + visualizationType: 'lnsDatatable', + state: { + visualization: { + layerId, + layerType: 'data', + columns: [ + { + columnId: 'a9b43606-7ff7-46ae-a47c-85bed80fab9a', + }, + { + columnId: '21cc4a49-3780-4b1a-be28-f02fa5303d24', + }, + ], + headerRowHeight: 'custom', + headerRowHeightLines: 0.6, + rowHeight: 'custom', + rowHeightLines: 0.8, + }, + query: { + query: '', + language: 'kuery', + }, + filters: extraOptions?.filters ? extraOptions.filters : [], + datasourceStates: { + formBased: { + layers: { + [layerId]: { + columns: { + 'a9b43606-7ff7-46ae-a47c-85bed80fab9a': { + label: 'Filters', + dataType: 'string', + operationType: 'filters', + scale: 'ordinal', + isBucketed: true, + params: { + filters: [ + { + input: { + query: `${stackByField}: "critical"`, + language: 'kuery', + }, + label: 'Critical', }, - label: 'Critical', - }, - { - label: 'High', - input: { - query: 'kibana.alert.severity : "high" ', - language: 'kuery', + { + label: 'High', + input: { + query: `${stackByField} : "high"`, + language: 'kuery', + }, }, - }, - { - input: { - query: 'kibana.alert.severity: "medium"', - language: 'kuery', + { + input: { + query: `${stackByField}: "medium"`, + language: 'kuery', + }, + label: 'Medium', }, - label: 'Medium', - }, - { - input: { - query: 'kibana.alert.severity : "low" ', - language: 'kuery', + { + input: { + query: `${stackByField} : "low"`, + language: 'kuery', + }, + label: 'Low', }, - label: 'Low', - }, - ], + ], + }, }, - }, - '21cc4a49-3780-4b1a-be28-f02fa5303d24': { - label: 'Count of records', - dataType: 'number', - operationType: 'count', - isBucketed: false, - scale: 'ratio', - sourceField: '___records___', - filter: { - query: '', - language: 'kuery', - }, - params: { - emptyAsNull: true, + '21cc4a49-3780-4b1a-be28-f02fa5303d24': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + filter: { + query: '', + language: 'kuery', + }, + params: { + emptyAsNull: true, + }, }, }, + columnOrder: [ + 'a9b43606-7ff7-46ae-a47c-85bed80fab9a', + '21cc4a49-3780-4b1a-be28-f02fa5303d24', + ], + sampling: 1, + incompleteColumns: {}, }, - columnOrder: [ - 'a9b43606-7ff7-46ae-a47c-85bed80fab9a', - '21cc4a49-3780-4b1a-be28-f02fa5303d24', - ], - sampling: 1, - incompleteColumns: {}, }, }, + textBased: { + layers: {}, + }, }, - textBased: { - layers: {}, - }, + internalReferences: [], + adHocDataViews: {}, }, - internalReferences: [], - adHocDataViews: {}, - }, - references: [ - { - type: 'index-pattern', - id: '{dataViewId}', - name: 'indexpattern-datasource-layer-51ed355e-6e23-4038-a417-f653a1160370', - }, - { - type: 'index-pattern', - name: '22752b9b-cfcd-43f0-a6ee-27dd4893edcf', - id: '{dataViewId}', - }, - ], -}); + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: `indexpattern-datasource-layer-${layerId}`, + }, + { + type: 'index-pattern', + name: '22752b9b-cfcd-43f0-a6ee-27dd4893edcf', + id: '{dataViewId}', + }, + ], + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.test.ts new file mode 100644 index 00000000000000..109bf7da68beef --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.test.ts @@ -0,0 +1,63 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { mockExtraFilter, wrapper } from '../../../mocks'; + +import { useLensAttributes } from '../../../use_lens_attributes'; + +import { getAlertsByStatusAttributes } from './alerts_by_status_donut'; + +jest.mock('uuid', () => ({ + v4: jest.fn().mockReturnValue('b9b43606-7ff7-46ae-a47c-85bed80fab9a'), +})); + +jest.mock('../../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + dataViewId: 'security-solution-my-test', + indicesExist: true, + selectedPatterns: ['signal-index'], + }), +})); + +jest.mock('../../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + pageName: 'alerts', + }, + ]), +})); + +describe('getAlertsByStatusAttributes', () => { + it('should render without extra options', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getAlertsByStatusAttributes, + stackByField: 'kibana.alert.workflow_status', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - filters', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { + filters: mockExtraFilter, + }, + getLensAttributes: getAlertsByStatusAttributes, + stackByField: 'kibana.alert.workflow_status', + }), + { wrapper } + ); + + expect(result?.current?.state.filters).toEqual(expect.arrayContaining(mockExtraFilter)); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts index 7de608419b83ad..46d2307645c224 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts @@ -4,14 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import type { GetLensAttributes, LensAttributes } from '../../../types'; +import { v4 as uuidv4 } from 'uuid'; +import type { GetLensAttributes } from '../../../types'; export const getAlertsByStatusAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.workflow_status', extraOptions -) => - ({ +) => { + const layerId = uuidv4(); + return { title: 'Alerts', description: '', visualizationType: 'lnsPie', @@ -20,7 +21,7 @@ export const getAlertsByStatusAttributes: GetLensAttributes = ( shape: 'donut', layers: [ { - layerId: '51ed355e-6e23-4038-a417-f653a1160370', + layerId, primaryGroups: ['a9b43606-7ff7-46ae-a47c-85bed80fab9a'], metrics: ['21cc4a49-3780-4b1a-be28-f02fa5303d24'], numberDisplay: 'value', @@ -66,7 +67,7 @@ export const getAlertsByStatusAttributes: GetLensAttributes = ( datasourceStates: { formBased: { layers: { - '51ed355e-6e23-4038-a417-f653a1160370': { + [layerId]: { columns: { 'a9b43606-7ff7-46ae-a47c-85bed80fab9a': { label: 'Filters', @@ -143,7 +144,7 @@ export const getAlertsByStatusAttributes: GetLensAttributes = ( { type: 'index-pattern', id: '{dataViewId}', - name: 'indexpattern-datasource-layer-51ed355e-6e23-4038-a417-f653a1160370', + name: `indexpattern-datasource-layer-${layerId}`, }, { type: 'index-pattern', @@ -151,4 +152,5 @@ export const getAlertsByStatusAttributes: GetLensAttributes = ( id: '{dataViewId}', }, ], - } as LensAttributes); + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts index b3ccb0b0f4b733..bf84e4999faa53 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts @@ -11,6 +11,10 @@ import { useLensAttributes } from '../../../use_lens_attributes'; import { getAlertsHistogramLensAttributes } from './alerts_histogram'; +jest.mock('uuid', () => ({ + v4: jest.fn().mockReturnValue('0039eb0c-9a1a-4687-ae54-0f4e239bec75'), +})); + jest.mock('../../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ dataViewId: 'security-solution-my-test', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts index 557bfd50fce72d..18b8bebd700e3e 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts @@ -4,14 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import type { GetLensAttributes, LensAttributes } from '../../../types'; +import { v4 as uuidv4 } from 'uuid'; +import type { GetLensAttributes } from '../../../types'; export const getAlertsHistogramLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', extraOptions -) => - ({ +) => { + const layerId = uuidv4(); + return { title: 'Alerts', description: '', visualizationType: 'lnsXY', @@ -21,13 +22,12 @@ export const getAlertsHistogramLensAttributes: GetLensAttributes = ( legend: { isVisible: true, position: 'right', - isInside: true, }, valueLabels: 'hide', preferredSeriesType: 'bar_stacked', layers: [ { - layerId: '0039eb0c-9a1a-4687-ae54-0f4e239bec75', + layerId, accessors: ['e09e0380-0740-4105-becc-0a4ca12e3944'], position: 'top', seriesType: 'bar_stacked', @@ -58,7 +58,7 @@ export const getAlertsHistogramLensAttributes: GetLensAttributes = ( datasourceStates: { formBased: { layers: { - '0039eb0c-9a1a-4687-ae54-0f4e239bec75': { + [layerId]: { columns: { 'aac9d7d0-13a3-480a-892b-08207a787926': { label: '@timestamp', @@ -119,7 +119,8 @@ export const getAlertsHistogramLensAttributes: GetLensAttributes = ( { type: 'index-pattern', id: '{dataViewId}', - name: 'indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75', + name: `indexpattern-datasource-layer-${layerId}`, }, ], - } as LensAttributes); + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts index 34f9cb45eab403..e8457e8dfb5333 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts @@ -11,6 +11,10 @@ import { useLensAttributes } from '../../../use_lens_attributes'; import { getAlertsTableLensAttributes } from './alerts_table'; +jest.mock('uuid', () => ({ + v4: jest.fn().mockReturnValue('4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b'), +})); + jest.mock('../../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ dataViewId: 'security-solution-my-test', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts index cea14505f2ebcb..50bce1a9ba074c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts @@ -4,133 +4,137 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { v4 as uuidv4 } from 'uuid'; import type { GetLensAttributes } from '../../../types'; export const getAlertsTableLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', extraOptions -) => ({ - title: 'Alerts', - description: '', - visualizationType: 'lnsDatatable', - state: { - visualization: { - columns: [ - { - columnId: '2881fedd-54b7-42ba-8c97-5175dec86166', - isTransposed: false, - width: 362, - }, - { - columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', - isTransposed: false, - }, - { - columnId: '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', - isTransposed: false, +) => { + const layerId = uuidv4(); + return { + title: 'Alerts', + description: '', + visualizationType: 'lnsDatatable', + state: { + visualization: { + columns: [ + { + columnId: '2881fedd-54b7-42ba-8c97-5175dec86166', + isTransposed: false, + width: 362, + }, + { + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + isTransposed: false, + }, + { + columnId: '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + isTransposed: false, + }, + ], + layerId, + layerType: 'data', + paging: { + size: 10, + enabled: true, }, - ], - layerId: '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', - layerType: 'data', - paging: { - size: 10, - enabled: true, }, - }, - query: { - query: '', - language: 'kuery', - }, - filters: extraOptions?.filters ? extraOptions.filters : [], - datasourceStates: { - formBased: { - layers: { - '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b': { - columns: { - '2881fedd-54b7-42ba-8c97-5175dec86166': { - label: `Top values of ${stackByField}`, - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: stackByField, - isBucketed: true, - params: { - size: 1000, - orderBy: { - type: 'column', - columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', - }, - orderDirection: 'desc', - otherBucket: true, - missingBucket: false, - parentFormat: { - id: 'terms', + query: { + query: '', + language: 'kuery', + }, + filters: extraOptions?.filters ? extraOptions.filters : [], + datasourceStates: { + formBased: { + layers: { + [layerId]: { + columns: { + '2881fedd-54b7-42ba-8c97-5175dec86166': { + label: `Top values of ${stackByField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: stackByField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, }, - include: [], - exclude: [], - includeIsRegex: false, - excludeIsRegex: false, }, - }, - 'f04a71a3-399f-4d32-9efc-8a005e989991': { - label: `Count of ${extraOptions?.breakdownField}`, - dataType: 'number', - operationType: 'count', - isBucketed: false, - scale: 'ratio', - sourceField: extraOptions?.breakdownField, - params: { - emptyAsNull: true, - }, - }, - '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { - label: `Top values of ${extraOptions?.breakdownField}`, - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: extraOptions?.breakdownField, - isBucketed: true, - params: { - size: 1000, - orderBy: { - type: 'column', - columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + 'f04a71a3-399f-4d32-9efc-8a005e989991': { + label: `Count of ${extraOptions?.breakdownField}`, + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: extraOptions?.breakdownField, + params: { + emptyAsNull: true, }, - orderDirection: 'desc', - otherBucket: true, - missingBucket: false, - parentFormat: { - id: 'terms', + }, + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { + label: `Top values of ${extraOptions?.breakdownField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: extraOptions?.breakdownField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, }, - include: [], - exclude: [], - includeIsRegex: false, - excludeIsRegex: false, }, }, + columnOrder: [ + '2881fedd-54b7-42ba-8c97-5175dec86166', + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + 'f04a71a3-399f-4d32-9efc-8a005e989991', + ], + sampling: 1, + incompleteColumns: {}, }, - columnOrder: [ - '2881fedd-54b7-42ba-8c97-5175dec86166', - '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', - 'f04a71a3-399f-4d32-9efc-8a005e989991', - ], - sampling: 1, - incompleteColumns: {}, }, }, + textBased: { + layers: {}, + }, }, - textBased: { - layers: {}, - }, + internalReferences: [], + adHocDataViews: {}, }, - internalReferences: [], - adHocDataViews: {}, - }, - references: [ - { - type: 'index-pattern', - id: '{dataViewId}', - name: 'indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', - }, - ], -}); + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: `indexpattern-datasource-layer-${layerId}`, + }, + ], + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts index af80ae5a3f5b69..e09feb8ddb584f 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts @@ -11,6 +11,10 @@ import { useLensAttributes } from '../../../use_lens_attributes'; import { getAlertsTreemapLensAttributes } from './alerts_treemap'; +jest.mock('uuid', () => ({ + v4: jest.fn().mockReturnValue('4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b'), +})); + jest.mock('../../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ dataViewId: 'security-solution-my-test', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts index 719574b6aa724b..62c62f29de32c8 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts @@ -4,130 +4,134 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { v4 as uuidv4 } from 'uuid'; import type { GetLensAttributes } from '../../../types'; export const getAlertsTreemapLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', extraOptions -) => ({ - title: 'Alerts', - description: '', - visualizationType: 'lnsPie', - state: { - visualization: { - shape: 'treemap', - layers: [ - { - layerId: '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', - primaryGroups: [ - '2881fedd-54b7-42ba-8c97-5175dec86166', - '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', - ], - metrics: ['f04a71a3-399f-4d32-9efc-8a005e989991'], - numberDisplay: 'value', - categoryDisplay: 'default', - legendDisplay: 'show', - nestedLegend: true, - layerType: 'data', - legendSize: 'xlarge', - legendPosition: 'left', - }, - ], - }, - query: { - query: '', - language: 'kuery', - }, - filters: extraOptions?.filters ? extraOptions.filters : [], - datasourceStates: { - formBased: { - layers: { - '4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b': { - columns: { - '2881fedd-54b7-42ba-8c97-5175dec86166': { - label: `Top values of ${stackByField}`, - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: stackByField, - isBucketed: true, - params: { - size: 1000, - orderBy: { - type: 'column', - columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', - }, - orderDirection: 'desc', - otherBucket: true, - missingBucket: false, - parentFormat: { - id: 'terms', +) => { + const layerId = uuidv4(); + return { + title: 'Alerts', + description: '', + visualizationType: 'lnsPie', + state: { + visualization: { + shape: 'treemap', + layers: [ + { + layerId, + primaryGroups: [ + '2881fedd-54b7-42ba-8c97-5175dec86166', + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + ], + metrics: ['f04a71a3-399f-4d32-9efc-8a005e989991'], + numberDisplay: 'value', + categoryDisplay: 'default', + legendDisplay: 'show', + nestedLegend: true, + layerType: 'data', + legendSize: 'xlarge', + legendPosition: 'left', + }, + ], + }, + query: { + query: '', + language: 'kuery', + }, + filters: extraOptions?.filters ? extraOptions.filters : [], + datasourceStates: { + formBased: { + layers: { + [layerId]: { + columns: { + '2881fedd-54b7-42ba-8c97-5175dec86166': { + label: `Top values of ${stackByField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: stackByField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, }, - include: [], - exclude: [], - includeIsRegex: false, - excludeIsRegex: false, }, - }, - '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { - label: `Top values of ${extraOptions?.breakdownField}`, - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: extraOptions?.breakdownField, - isBucketed: true, - params: { - size: 1000, - orderBy: { - type: 'column', - columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { + label: `Top values of ${extraOptions?.breakdownField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: extraOptions?.breakdownField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, }, - orderDirection: 'desc', - otherBucket: true, - missingBucket: false, - parentFormat: { - id: 'terms', - }, - include: [], - exclude: [], - includeIsRegex: false, - excludeIsRegex: false, }, - }, - 'f04a71a3-399f-4d32-9efc-8a005e989991': { - label: 'Maximum of kibana.alert.risk_score', - dataType: 'number', - operationType: 'max', - sourceField: 'kibana.alert.risk_score', - isBucketed: false, - scale: 'ratio', - params: { - emptyAsNull: true, + 'f04a71a3-399f-4d32-9efc-8a005e989991': { + label: 'Maximum of kibana.alert.risk_score', + dataType: 'number', + operationType: 'max', + sourceField: 'kibana.alert.risk_score', + isBucketed: false, + scale: 'ratio', + params: { + emptyAsNull: true, + }, }, }, + columnOrder: [ + '2881fedd-54b7-42ba-8c97-5175dec86166', + '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', + 'f04a71a3-399f-4d32-9efc-8a005e989991', + ], + sampling: 1, + incompleteColumns: {}, }, - columnOrder: [ - '2881fedd-54b7-42ba-8c97-5175dec86166', - '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', - 'f04a71a3-399f-4d32-9efc-8a005e989991', - ], - sampling: 1, - incompleteColumns: {}, }, }, + textBased: { + layers: {}, + }, }, - textBased: { - layers: {}, - }, + internalReferences: [], + adHocDataViews: {}, }, - internalReferences: [], - adHocDataViews: {}, - }, - references: [ - { - type: 'index-pattern', - id: '{dataViewId}', - name: 'indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b', - }, - ], -}); + references: [ + { + type: 'index-pattern', + id: '{dataViewId}', + name: `indexpattern-datasource-layer-${layerId}`, + }, + ], + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts new file mode 100644 index 00000000000000..dc0bb2ca33dc5c --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts @@ -0,0 +1,73 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { mockRulePreviewFilter, wrapper } from '../../../mocks'; +import { v4 as uuidv4 } from 'uuid'; + +import { useLensAttributes } from '../../../use_lens_attributes'; + +import { getRulePreviewLensAttributes } from './rule_preview'; +const mockLayerId = 'mockLayerId'; +const mockInternalReferenceId = 'mockInternalReferenceId'; +const mockRuleId = 'mockRuleId'; + +jest.mock('uuid', () => ({ + v4: jest.fn(), +})); + +jest.mock('../../../../../containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ + dataViewId: 'security-solution-my-test', + indicesExist: true, + selectedPatterns: ['signal-index'], + }), +})); + +jest.mock('../../../../../utils/route/use_route_spy', () => ({ + useRouteSpy: jest.fn().mockReturnValue([ + { + pageName: 'alerts', + }, + ]), +})); + +describe('getRulePreviewLensAttributes', () => { + beforeEach(() => { + jest.clearAllMocks(); + (uuidv4 as jest.Mock).mockReturnValueOnce(mockLayerId).mockReturnValue(mockInternalReferenceId); + }); + it('should render without extra options', () => { + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getRulePreviewLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current).toMatchSnapshot(); + }); + + it('should render with extra options - filters', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { + ruleId: mockRuleId, + }, + getLensAttributes: getRulePreviewLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + expect(result?.current?.state.filters).toEqual( + expect.arrayContaining(mockRulePreviewFilter(mockInternalReferenceId, mockRuleId)) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts index fc7071cc8afffd..042b82a3c6b577 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts @@ -4,159 +4,163 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { v4 as uuidv4 } from 'uuid'; import type { GetLensAttributes } from '../../../types'; export const getRulePreviewLensAttributes: GetLensAttributes = ( stackByField = 'event.category', extraOptions -) => ({ - title: 'alert preview', - description: '', - visualizationType: 'lnsXY', - state: { - visualization: { - title: 'Empty XY chart', - legend: { - isVisible: false, - position: 'left', - }, - valueLabels: 'hide', - preferredSeriesType: 'bar_stacked', - layers: [ - { - layerId: '1469e8f5-c685-4f37-96f4-2084c0537bf7', - accessors: ['9c89324b-0c59-4403-9698-d989a09dc5a8'], - position: 'top', - seriesType: 'bar_stacked', - showGridlines: false, - layerType: 'data', - xAccessor: 'eba07b4d-766d-49d7-8435-d40367d3d055', - splitAccessor: 'e92c8920-0449-4564-81f4-8945517817a4', +) => { + const layerId = uuidv4(); + const internalReferenceId = uuidv4(); + return { + title: 'Rule preview', + description: '', + visualizationType: 'lnsXY', + state: { + visualization: { + title: 'Empty XY chart', + legend: { + isVisible: false, + position: 'left', }, - ], - valuesInLegend: true, - yTitle: '', - axisTitlesVisibilitySettings: { - x: false, - yLeft: false, - yRight: true, - }, - }, - query: { - query: '', - language: 'kuery', - }, - filters: [ - { - meta: { - disabled: false, - negate: false, - alias: null, - index: '1f76cd3c-7267-424d-9bbc-213c8c3def61', - key: 'kibana.alert.rule.uuid', - field: 'kibana.alert.rule.uuid', - params: { - query: extraOptions?.ruleId, + valueLabels: 'hide', + preferredSeriesType: 'bar_stacked', + layers: [ + { + layerId, + accessors: ['9c89324b-0c59-4403-9698-d989a09dc5a8'], + position: 'top', + seriesType: 'bar_stacked', + showGridlines: false, + layerType: 'data', + xAccessor: 'eba07b4d-766d-49d7-8435-d40367d3d055', + splitAccessor: 'e92c8920-0449-4564-81f4-8945517817a4', }, - type: 'phrase', + ], + valuesInLegend: true, + yTitle: '', + axisTitlesVisibilitySettings: { + x: false, + yLeft: false, + yRight: true, }, - query: { - match_phrase: { - 'kibana.alert.rule.uuid': extraOptions?.ruleId, + }, + query: { + query: '', + language: 'kuery', + }, + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + index: internalReferenceId, + key: 'kibana.alert.rule.uuid', + field: 'kibana.alert.rule.uuid', + params: { + query: extraOptions?.ruleId, + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'kibana.alert.rule.uuid': extraOptions?.ruleId, + }, }, }, - }, - ], - datasourceStates: { - formBased: { - layers: { - '1469e8f5-c685-4f37-96f4-2084c0537bf7': { - columns: { - '9c89324b-0c59-4403-9698-d989a09dc5a8': { - label: 'Count of records', - dataType: 'number', - operationType: 'count', - isBucketed: false, - scale: 'ratio', - sourceField: '___records___', - params: { - emptyAsNull: true, - }, - }, - 'eba07b4d-766d-49d7-8435-d40367d3d055': { - label: '@timestamp', - dataType: 'date', - operationType: 'date_histogram', - sourceField: '@timestamp', - isBucketed: true, - scale: 'interval', - params: { - interval: 'auto', - includeEmptyRows: true, - dropPartials: false, + ], + datasourceStates: { + formBased: { + layers: { + [layerId]: { + columns: { + '9c89324b-0c59-4403-9698-d989a09dc5a8': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + params: { + emptyAsNull: true, + }, }, - }, - 'e92c8920-0449-4564-81f4-8945517817a4': { - label: `Top 10 values of ${stackByField}`, - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: stackByField, - isBucketed: true, - params: { - size: 10, - orderBy: { - type: 'column', - columnId: '9c89324b-0c59-4403-9698-d989a09dc5a8', + 'eba07b4d-766d-49d7-8435-d40367d3d055': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { + interval: 'auto', + includeEmptyRows: true, + dropPartials: false, }, - orderDirection: 'desc', - otherBucket: true, - missingBucket: false, - parentFormat: { - id: 'terms', + }, + 'e92c8920-0449-4564-81f4-8945517817a4': { + label: `Top 10 values of ${stackByField}`, + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: stackByField, + isBucketed: true, + params: { + size: 10, + orderBy: { + type: 'column', + columnId: '9c89324b-0c59-4403-9698-d989a09dc5a8', + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, }, - include: [], - exclude: [], - includeIsRegex: false, - excludeIsRegex: false, }, }, + columnOrder: [ + 'e92c8920-0449-4564-81f4-8945517817a4', + 'eba07b4d-766d-49d7-8435-d40367d3d055', + '9c89324b-0c59-4403-9698-d989a09dc5a8', + ], + sampling: 1, + incompleteColumns: {}, }, - columnOrder: [ - 'e92c8920-0449-4564-81f4-8945517817a4', - 'eba07b4d-766d-49d7-8435-d40367d3d055', - '9c89324b-0c59-4403-9698-d989a09dc5a8', - ], - sampling: 1, - incompleteColumns: {}, }, }, + textBased: { + layers: {}, + }, }, - textBased: { - layers: {}, - }, - }, - internalReferences: [ - { - type: 'index-pattern', - id: '1f76cd3c-7267-424d-9bbc-213c8c3def61', - name: 'indexpattern-datasource-layer-1469e8f5-c685-4f37-96f4-2084c0537bf7', - }, - ], - adHocDataViews: { - '1f76cd3c-7267-424d-9bbc-213c8c3def61': { - id: '1f76cd3c-7267-424d-9bbc-213c8c3def61', - title: `.preview.alerts-security.alerts-${extraOptions?.spaceId}`, - timeFieldName: '@timestamp', - sourceFilters: [], - fieldFormats: {}, - runtimeFieldMap: {}, - fieldAttrs: {}, - allowNoIndex: false, - name: `.preview.alerts-security.alerts-${extraOptions?.spaceId}`, + internalReferences: [ + { + type: 'index-pattern', + id: internalReferenceId, + name: `indexpattern-datasource-layer-${layerId}`, + }, + ], + adHocDataViews: { + [internalReferenceId]: { + id: internalReferenceId, + title: `.preview.alerts-security.alerts-${extraOptions?.spaceId}`, + timeFieldName: '@timestamp', + sourceFilters: [], + fieldFormats: {}, + runtimeFieldMap: {}, + fieldAttrs: {}, + allowNoIndex: false, + name: `.preview.alerts-security.alerts-${extraOptions?.spaceId}`, + }, }, }, - }, - references: [], -}); + references: [], + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts index 87d246fc2350b6..a9a1c3951de5aa 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts @@ -12,6 +12,10 @@ import { useLensAttributes } from '../../use_lens_attributes'; import { getEventsHistogramLensAttributes } from './events'; +jest.mock('uuid', () => ({ + v4: jest.fn().mockReturnValue('0039eb0c-9a1a-4687-ae54-0f4e239bec75'), +})); + jest.mock('../../../../containers/sourcerer', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ selectedPatterns: ['auditbeat-mytest-*'], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts index e48f6aa6c1a87f..21086165eaf5f7 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts @@ -4,13 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import type { GetLensAttributes, LensAttributes } from '../../types'; +import { v4 as uuidv4 } from 'uuid'; +import type { GetLensAttributes } from '../../types'; export const getEventsHistogramLensAttributes: GetLensAttributes = ( stackByField = 'event.action' -) => - ({ +) => { + const layerId = uuidv4(); + + return { title: 'Events', description: '', visualizationType: 'lnsXY', @@ -25,7 +27,7 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( preferredSeriesType: 'bar_stacked', layers: [ { - layerId: '0039eb0c-9a1a-4687-ae54-0f4e239bec75', + layerId, accessors: ['e09e0380-0740-4105-becc-0a4ca12e3944'], position: 'top', seriesType: 'bar_stacked', @@ -55,7 +57,7 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( datasourceStates: { formBased: { layers: { - '0039eb0c-9a1a-4687-ae54-0f4e239bec75': { + [layerId]: { columns: { 'aac9d7d0-13a3-480a-892b-08207a787926': { label: '@timestamp', @@ -113,7 +115,8 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( { type: 'index-pattern', id: '{dataViewId}', - name: 'indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75', + name: `indexpattern-datasource-layer-${layerId}`, }, ], - } as LensAttributes); + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 284caf8ad908e9..985a9881d330b1 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -135,7 +135,6 @@ const LensEmbeddableComponent: React.FC = ({ return; } const data = getRequestsAndResponses(adapters?.requests?.getRequests()); - setVisualizationData({ requests: data.requests, responses: data.responses, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx index e0f6a5ab56d6d7..77fa5b02cede09 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx @@ -129,3 +129,50 @@ export const mockAttributes: LensAttributes = { }, ], }; + +export const mockExtraFilter = [ + { + meta: { + type: 'phrases', + key: '_index', + params: ['.alerts-security.alerts-default'], + alias: null, + negate: false, + disabled: false, + }, + query: { + bool: { + should: [ + { + match_phrase: { + _index: '.alerts-security.alerts-default', + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, +]; + +export const mockRulePreviewFilter = (internalReferenceId: string, ruleId: string) => [ + { + meta: { + disabled: false, + negate: false, + alias: null, + index: internalReferenceId, + key: 'kibana.alert.rule.uuid', + field: 'kibana.alert.rule.uuid', + params: { + query: ruleId, + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'kibana.alert.rule.uuid': ruleId, + }, + }, + }, +]; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index db2ba0617313fb..769a6720bc76fd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -56,7 +56,7 @@ interface AlertsCountPanelProps { stackByWidth?: number; title?: React.ReactNode; } -const ChartHeight = '180px'; +const CHART_HEIGHT = '180px'; export const AlertsCountPanel = memo( ({ @@ -221,7 +221,7 @@ export const AlertsCountPanel = memo( extraActions={extraActions} extraOptions={extraVisualizationOptions} getLensAttributes={getLensAttributes} - height={ChartHeight} + height={CHART_HEIGHT} id={`${uniqueQueryId}-embeddable`} inspectTitle={inspectTitle} scopeId={SourcererScopeName.detections} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index cadb11de708aa4..91b27192111cb8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -188,13 +188,6 @@ export const AlertsHistogramPanel = memo( const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const timerange = useMemo(() => ({ from, to }), [from, to]); - const extraVisualizationOptions = useMemo( - () => ({ - filters, - }), - [filters] - ); - const { loading: isLoadingAlerts, data: alertsData, @@ -425,7 +418,9 @@ export const AlertsHistogramPanel = memo( void; + interface AlertsChartsPanelProps { - addFilter?: ({ field, value }: { field: string; value: string | number }) => void; + addFilter?: FieldFilter; data: ParsedSeverityData; filters?: Filter[]; isLoading: boolean; @@ -108,7 +110,7 @@ export const SeverityLevelChart: React.FC = ({ extraOptions={extraOptions} height="135px" getLensAttributes={getAlertsBySeverityTableAttributes} - stackByField="kibana.alert.workflow_status" + stackByField="kibana.alert.severity" scopeId={SourcererScopeName.detections} id={`${uniqueQueryId}-table`} /> diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx index 355a06356ee84d..9cfd2795582003 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx @@ -56,7 +56,7 @@ const FullScreenContainer = styled.div<{ $isFullScreen: boolean }>` export const ID = 'previewHistogram'; -export const CHART_HEIGHT = 150; +const CHART_HEIGHT = 150; interface PreviewHistogramProps { previewId: string; @@ -119,6 +119,7 @@ export const PreviewHistogram = ({ const { globalFullScreen } = useGlobalFullScreen(); const previousPreviewId = usePrevious(previewId); + const previewQueryId = `${ID}-${previewId}`; useEffect(() => { if (previousPreviewId !== previewId && totalCount > 0) { @@ -131,13 +132,22 @@ export const PreviewHistogram = ({ useEffect((): void => { if (!isLoading && !isInitializing) { setQuery({ - id: `${ID}-${previewId}`, + id: previewQueryId, inspect, loading: isLoading, refetch, }); } - }, [setQuery, inspect, isLoading, isInitializing, refetch, previewId, isChartEmbeddablesEnabled]); + }, [ + setQuery, + inspect, + isLoading, + isInitializing, + refetch, + previewId, + isChartEmbeddablesEnabled, + previewQueryId, + ]); const barConfig = useMemo( (): ChartSeriesConfigs => getHistogramConfig(endDate, startDate, !isEqlRule), @@ -180,7 +190,7 @@ export const PreviewHistogram = ({ Date: Wed, 25 Jan 2023 12:26:57 +0000 Subject: [PATCH 48/72] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- x-pack/plugins/security_solution/public/helpers.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/helpers.tsx b/x-pack/plugins/security_solution/public/helpers.tsx index 41006ae42835be..7a0f918d4e169d 100644 --- a/x-pack/plugins/security_solution/public/helpers.tsx +++ b/x-pack/plugins/security_solution/public/helpers.tsx @@ -20,13 +20,10 @@ import { CASES_FEATURE_ID, CASES_PATH, EXCEPTIONS_PATH, - HOSTS_PATH, LANDING_PATH, - NETWORK_PATH, RULES_PATH, SERVER_APP_ID, THREAT_INTELLIGENCE_PATH, - USERS_PATH, } from '../common/constants'; import type { FactoryQueryTypes, From c45a535d8abdfe3bd581a9b776662338c69f8311 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 25 Jan 2023 13:33:18 +0000 Subject: [PATCH 49/72] fix types --- x-pack/plugins/security_solution/public/helpers.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/helpers.tsx b/x-pack/plugins/security_solution/public/helpers.tsx index 41006ae42835be..7a0f918d4e169d 100644 --- a/x-pack/plugins/security_solution/public/helpers.tsx +++ b/x-pack/plugins/security_solution/public/helpers.tsx @@ -20,13 +20,10 @@ import { CASES_FEATURE_ID, CASES_PATH, EXCEPTIONS_PATH, - HOSTS_PATH, LANDING_PATH, - NETWORK_PATH, RULES_PATH, SERVER_APP_ID, THREAT_INTELLIGENCE_PATH, - USERS_PATH, } from '../common/constants'; import type { FactoryQueryTypes, From b54143b2a37dc9358a43ba61214e4a7eccd74c37 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 25 Jan 2023 15:29:44 +0000 Subject: [PATCH 50/72] review --- .../matrix_histogram/chart_content.tsx | 29 +++++++++++++++++++ .../components/matrix_histogram/index.tsx | 9 ++---- .../common/alerts/alerts_by_severity_table.ts | 3 +- .../common/alerts/alerts_by_status_donut.ts | 2 +- .../common/alerts/alerts_histogram.ts | 2 +- .../common/alerts/alerts_table.ts | 7 ++--- .../common/alerts/alerts_treemap.ts | 3 +- .../common/alerts/rule_preview.ts | 5 ++-- .../lens_attributes/common/events.ts | 4 +-- .../visualization_actions/use_actions.test.ts | 12 ++++---- .../visualization_actions/use_actions.ts | 8 ++--- .../severity_donut/severity_level_chart.tsx | 7 +++-- .../detection_engine/chart_panels/index.tsx | 2 +- 13 files changed, 60 insertions(+), 33 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/matrix_histogram/chart_content.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/chart_content.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/chart_content.tsx new file mode 100644 index 00000000000000..65d3774af1eef0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/chart_content.tsx @@ -0,0 +1,29 @@ +/* + * 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 from 'react'; +import type { BarChartComponentProps } from '../charts/barchart'; +import { BarChart } from '../charts/barchart'; +import { MatrixLoader } from './matrix_loader'; + +const MatrixHistogramChartContentComponent = ({ + isInitialLoading, + barChart, + configs, + stackByField, + scopeId, +}: BarChartComponentProps & { isInitialLoading: boolean }) => { + return isInitialLoading ? ( + + ) : ( + + ); +}; + +export const MatrixHistogramChartContent = React.memo(MatrixHistogramChartContentComponent); + +MatrixHistogramChartContentComponent.displayName = 'MatrixHistogramChartContentComponent'; diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index f78d3882464607..63a8158423193b 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -12,9 +12,7 @@ import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSelect, EuiSpacer } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import * as i18n from './translations'; -import { BarChart } from '../charts/barchart'; import { HeaderSection } from '../header_section'; -import { MatrixLoader } from './matrix_loader'; import { Panel } from '../panel'; import { getBarchartConfigs, getCustomChartData } from './utils'; import { useMatrixHistogramCombined } from '../../containers/matrix_histogram'; @@ -37,6 +35,7 @@ import { useQueryToggle } from '../../containers/query_toggle'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../visualization_actions/utils'; import { VisualizationEmbeddable } from '../visualization_actions/visualization_embeddable'; +import { MatrixHistogramChartContent } from './chart_content'; export type MatrixHistogramComponentProps = MatrixHistogramProps & Omit & { @@ -73,7 +72,6 @@ const HistogramPanel = styled(Panel)<{ height?: number }>` const CHART_HEIGHT = '150px'; -// eslint-disable-next-line complexity export const MatrixHistogramComponent: React.FC = ({ chartHeight, defaultStackByOption, @@ -312,10 +310,9 @@ export const MatrixHistogramComponent: React.FC = stackByField={selectedStackByOption.value} timerange={timerange} /> - ) : isInitialLoading ? ( - ) : ( - { - const layerId = uuidv4(); return { title: 'Alerts', description: '', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts index 46d2307645c224..33bc6827fa020d 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts @@ -6,12 +6,12 @@ */ import { v4 as uuidv4 } from 'uuid'; import type { GetLensAttributes } from '../../../types'; +const layerId = uuidv4(); export const getAlertsByStatusAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.workflow_status', extraOptions ) => { - const layerId = uuidv4(); return { title: 'Alerts', description: '', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts index 18b8bebd700e3e..8de8570f438dc9 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts @@ -6,12 +6,12 @@ */ import { v4 as uuidv4 } from 'uuid'; import type { GetLensAttributes } from '../../../types'; +const layerId = uuidv4(); export const getAlertsHistogramLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', extraOptions ) => { - const layerId = uuidv4(); return { title: 'Alerts', description: '', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts index 50bce1a9ba074c..678179855557cd 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts @@ -7,11 +7,12 @@ import { v4 as uuidv4 } from 'uuid'; import type { GetLensAttributes } from '../../../types'; +const layerId = uuidv4(); + export const getAlertsTableLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', extraOptions ) => { - const layerId = uuidv4(); return { title: 'Alerts', description: '', @@ -35,10 +36,6 @@ export const getAlertsTableLensAttributes: GetLensAttributes = ( ], layerId, layerType: 'data', - paging: { - size: 10, - enabled: true, - }, }, query: { query: '', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts index 62c62f29de32c8..dbf5d0c578f4df 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts @@ -7,11 +7,12 @@ import { v4 as uuidv4 } from 'uuid'; import type { GetLensAttributes } from '../../../types'; +const layerId = uuidv4(); + export const getAlertsTreemapLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', extraOptions ) => { - const layerId = uuidv4(); return { title: 'Alerts', description: '', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts index 042b82a3c6b577..33d59c358ea5fd 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts @@ -7,12 +7,13 @@ import { v4 as uuidv4 } from 'uuid'; import type { GetLensAttributes } from '../../../types'; +const layerId = uuidv4(); +const internalReferenceId = uuidv4(); + export const getRulePreviewLensAttributes: GetLensAttributes = ( stackByField = 'event.category', extraOptions ) => { - const layerId = uuidv4(); - const internalReferenceId = uuidv4(); return { title: 'Rule preview', description: '', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts index 21086165eaf5f7..cc983479e96500 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts @@ -7,11 +7,11 @@ import { v4 as uuidv4 } from 'uuid'; import type { GetLensAttributes } from '../../types'; +const layerId = uuidv4(); + export const getEventsHistogramLensAttributes: GetLensAttributes = ( stackByField = 'event.action' ) => { - const layerId = uuidv4(); - return { title: 'Events', description: '', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.ts index 9c8ccdfc51cfb9..0de49b52d66ffa 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.ts @@ -66,11 +66,11 @@ describe(`useActions`, () => { expect(result.current[0].id).toEqual('inspect'); expect(result.current[0].order).toEqual(4); - expect(result.current[1].id).toEqual('openInLens'); + expect(result.current[1].id).toEqual('addToNewCase'); expect(result.current[1].order).toEqual(3); - expect(result.current[2].id).toEqual('addToNewCase'); + expect(result.current[2].id).toEqual('addToExistingCase'); expect(result.current[2].order).toEqual(2); - expect(result.current[3].id).toEqual('addToExistingCase'); + expect(result.current[3].id).toEqual('openInLens'); expect(result.current[3].order).toEqual(1); }); @@ -110,11 +110,11 @@ describe(`useActions`, () => { expect(result.current[0].id).toEqual('inspect'); expect(result.current[0].order).toEqual(4); - expect(result.current[1].id).toEqual('openInLens'); + expect(result.current[1].id).toEqual('addToNewCase'); expect(result.current[1].order).toEqual(3); - expect(result.current[2].id).toEqual('addToNewCase'); + expect(result.current[2].id).toEqual('addToExistingCase'); expect(result.current[2].order).toEqual(2); - expect(result.current[3].id).toEqual('addToExistingCase'); + expect(result.current[3].id).toEqual('openInLens'); expect(result.current[3].order).toEqual(1); expect(result.current[4].id).toEqual('mockExtraAction'); expect(result.current[4].order).toEqual(0); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts index eb8097ee77ade7..111f67784ba56d 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts @@ -34,9 +34,9 @@ export const useActions = ({ const { navigateToPrefilledEditor } = lens; const [defaultActions, setDefaultActions] = useState([ 'inspect', - 'openInLens', 'addToNewCase', 'addToExistingCase', + 'openInLens', ]); useEffect(() => { @@ -141,7 +141,7 @@ const getOpenInLensAction = ({ callback }: { callback: () => void }): Action => async execute(context: ActionExecutionContext): Promise { callback(); }, - order: 3, + order: 1, }; }; @@ -168,7 +168,7 @@ const getAddToNewCaseAction = ({ callback(); }, disabled, - order: 2, + order: 3, }; }; @@ -222,6 +222,6 @@ const getAddToExistingCaseAction = ({ callback(); }, disabled, - order: 1, + order: 2, }; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx index d1cb426982c34c..17d31a3058e3a9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx @@ -71,6 +71,7 @@ export const SeverityLevelChart: React.FC = ({ }, }; + const style = isChartEmbeddablesEnabled ? { width: '50%' } : undefined; const onElementClick: ElementClickListener = useCallback( (event) => { const flattened = event.flat(2); @@ -102,7 +103,7 @@ export const SeverityLevelChart: React.FC = ({ titleSize="xs" /> - + {isChartEmbeddablesEnabled ? ( = ({ /> )} - + {isChartEmbeddablesEnabled ? ( = ({ scopeId={SourcererScopeName.detections} stackByField="kibana.alert.workflow_status" timerange={timerange} - width="135px" + width="100%" /> ) : ( = ({ updateCommonStackBy1(DEFAULT_STACK_BY_FIELD1); } }, - order: 0, + order: 5, }, ], [onReset, updateCommonStackBy0, updateCommonStackBy1] From 19726bc0acd3bdb5babb173b7e62d55b171e8872 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Jan 2023 10:04:53 +0000 Subject: [PATCH 51/72] move legend to the left side --- .../common/__snapshots__/authentication.test.ts.snap | 2 +- .../common/__snapshots__/event.test.ts.snap | 2 +- .../common/__snapshots__/external_alert.test.ts.snap | 2 +- .../__snapshots__/alerts_histogram.test.ts.snap | 4 ++-- .../alerts/__snapshots__/alerts_table.test.ts.snap | 12 ------------ .../common/alerts/alerts_histogram.ts | 2 +- .../common/alerts/rule_preview.test.ts | 5 +---- .../lens_attributes/common/authentication.ts | 2 +- .../lens_attributes/common/events.ts | 2 +- .../lens_attributes/common/external_alert.ts | 2 +- .../__snapshots__/dns_top_domains.test.ts.snap | 2 +- .../lens_attributes/network/dns_top_domains.ts | 2 +- 12 files changed, 12 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap index 84d29e3e7fd9d8..a00e6517914daa 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap @@ -251,7 +251,7 @@ Object { ], "legend": Object { "isVisible": true, - "position": "right", + "position": "left", }, "preferredSeriesType": "bar_stacked", "title": "Empty XY chart", diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap index 93bdbe2a0dca56..c3a84cdcea2c6b 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap @@ -175,7 +175,7 @@ Object { ], "legend": Object { "isVisible": true, - "position": "right", + "position": "left", }, "preferredSeriesType": "bar_stacked", "title": "Empty XY chart", diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap index d53499bd57c8a4..7d06e63032e951 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap @@ -206,7 +206,7 @@ Object { ], "legend": Object { "isVisible": true, - "position": "right", + "position": "left", }, "preferredSeriesType": "bar_stacked", "title": "Empty XY chart", diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap index 773d8892bc4882..05da68bed2a0ee 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap @@ -163,7 +163,7 @@ Object { ], "legend": Object { "isVisible": true, - "position": "right", + "position": "left", }, "preferredSeriesType": "bar_stacked", "title": "Empty XY chart", @@ -321,7 +321,7 @@ Object { ], "legend": Object { "isVisible": true, - "position": "right", + "position": "left", }, "preferredSeriesType": "bar_stacked", "title": "Empty XY chart", diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap index 4b609924b05ea7..58ecf5d44d0155 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap @@ -159,10 +159,6 @@ Object { ], "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", "layerType": "data", - "paging": Object { - "enabled": true, - "size": 10, - }, }, }, "title": "Alerts", @@ -353,10 +349,6 @@ Object { ], "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", "layerType": "data", - "paging": Object { - "enabled": true, - "size": 10, - }, }, }, "title": "Alerts", @@ -523,10 +515,6 @@ Object { ], "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", "layerType": "data", - "paging": Object { - "enabled": true, - "size": 10, - }, }, }, "title": "Alerts", diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts index 8de8570f438dc9..2de1ff0abadbf9 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts @@ -21,7 +21,7 @@ export const getAlertsHistogramLensAttributes: GetLensAttributes = ( title: 'Empty XY chart', legend: { isVisible: true, - position: 'right', + position: 'left', }, valueLabels: 'hide', preferredSeriesType: 'bar_stacked', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts index dc0bb2ca33dc5c..85b4a11bbc7f99 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts @@ -6,17 +6,15 @@ */ import { renderHook } from '@testing-library/react-hooks'; import { mockRulePreviewFilter, wrapper } from '../../../mocks'; -import { v4 as uuidv4 } from 'uuid'; import { useLensAttributes } from '../../../use_lens_attributes'; import { getRulePreviewLensAttributes } from './rule_preview'; -const mockLayerId = 'mockLayerId'; const mockInternalReferenceId = 'mockInternalReferenceId'; const mockRuleId = 'mockRuleId'; jest.mock('uuid', () => ({ - v4: jest.fn(), + v4: jest.fn().mockReturnValueOnce('mockLayerId').mockReturnValueOnce('mockInternalReferenceId'), })); jest.mock('../../../../../containers/sourcerer', () => ({ @@ -38,7 +36,6 @@ jest.mock('../../../../../utils/route/use_route_spy', () => ({ describe('getRulePreviewLensAttributes', () => { beforeEach(() => { jest.clearAllMocks(); - (uuidv4 as jest.Mock).mockReturnValueOnce(mockLayerId).mockReturnValue(mockInternalReferenceId); }); it('should render without extra options', () => { const { result } = renderHook( diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts index 4e69bac6287ecb..4378b74400aa2c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts @@ -20,7 +20,7 @@ export const authenticationLensAttributes: LensAttributes = { title: 'Empty XY chart', legend: { isVisible: true, - position: 'right', + position: 'left', }, valueLabels: 'hide', preferredSeriesType: 'bar_stacked', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts index cc983479e96500..e43a0f3d260110 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts @@ -21,7 +21,7 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( title: 'Empty XY chart', legend: { isVisible: true, - position: 'right', + position: 'left', }, valueLabels: 'hide', preferredSeriesType: 'bar_stacked', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts index f5a664b98161bb..44aa790332ba09 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts @@ -20,7 +20,7 @@ export const getExternalAlertLensAttributes: GetLensAttributes = ( title: 'Empty XY chart', legend: { isVisible: true, - position: 'right', + position: 'left', }, valueLabels: 'hide', preferredSeriesType: 'bar_stacked', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap index 38ee4c908fce06..a261abe99ffcfd 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap @@ -231,7 +231,7 @@ Object { ], "legend": Object { "isVisible": true, - "position": "right", + "position": "left", }, "preferredSeriesType": "bar", "tickLabelsVisibilitySettings": Object { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts index 0f195bdeaa8d4e..2e9ff922615187 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts @@ -17,7 +17,7 @@ export const dnsTopDomainsLensAttributes: LensAttributes = { visualization: { legend: { isVisible: true, - position: 'right', + position: 'left', }, valueLabels: 'hide', fittingFunction: 'None', From b65787c50114a6d4b2a624f522fa152a8858f917 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Jan 2023 11:38:25 +0000 Subject: [PATCH 52/72] disable chart actions for rule previews --- .../visualization_actions/lens_embeddable.tsx | 13 ++++++++++--- .../components/visualization_actions/types.ts | 1 + .../components/visualization_actions/use_actions.ts | 2 +- .../rules/rule_preview/preview_histogram.tsx | 1 + 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 985a9881d330b1..ae76d049e99ff2 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -48,8 +48,6 @@ const initVisualizationData: { isLoading: true, }; -const style = { height: '100%', minWidth: '100px' }; - const LensEmbeddableComponent: React.FC = ({ applyGlobalQueriesAndFilters = true, extraActions, @@ -65,7 +63,16 @@ const LensEmbeddableComponent: React.FC = ({ stackByField, timerange, width: wrapperWidth, + withActions = true, }) => { + const style = useMemo( + () => ({ + height: wrapperHeight ?? '100%', + minWidth: '100px', + width: wrapperWidth ?? '100%', + }), + [wrapperHeight, wrapperWidth] + ); const { lens } = useKibana().services; const dispatch = useDispatch(); const [isShowingModal, setIsShowingModal] = useState(false); @@ -98,7 +105,7 @@ const LensEmbeddableComponent: React.FC = ({ extraActions, inspectActionProps, timeRange: timerange, - withActions: true, + withActions, }); const handleCloseModal = useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index 5ef9b3eda38b43..b761aba8121009 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -57,6 +57,7 @@ export interface LensEmbeddableComponentProps { stackByField?: string; timerange: { from: string; to: string }; width?: string; + withActions?: boolean; } export enum RequestStatus { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts index 111f67784ba56d..504f30511cafcc 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts @@ -74,7 +74,7 @@ export const useActions = ({ const actions = useMemo( () => - defaultActions.reduce((acc, action) => { + defaultActions?.reduce((acc, action) => { if (action === 'inspect' && inspectActionProps != null) { return [ ...acc, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx index 9cfd2795582003..d06e7da09b1b13 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_histogram.tsx @@ -210,6 +210,7 @@ export const PreviewHistogram = ({ scopeId={SourcererScopeName.detections} stackByField={ruleType === 'machine_learning' ? 'host.name' : 'event.category'} timerange={timerange} + withActions={false} /> ) : ( Date: Thu, 26 Jan 2023 13:05:42 +0000 Subject: [PATCH 53/72] revert alert treemap and chart --- .../components/alerts_treemap_panel/index.tsx | 28 +- .../common/components/charts/donutchart.tsx | 10 +- .../alerts_by_severity_table.test.ts.snap | 161 ------ .../__snapshots__/alerts_treemap.test.ts.snap | 532 ------------------ .../alerts/alerts_by_severity_table.test.ts | 63 --- .../common/alerts/alerts_by_severity_table.ts | 131 ----- .../common/alerts/alerts_treemap.test.ts | 100 ---- .../common/alerts/alerts_treemap.ts | 138 ----- .../alerts_summary_charts_panel/index.tsx | 12 +- .../severity_donut/severity_level_chart.tsx | 73 +-- .../use_severity_chart_data.test.tsx | 9 +- .../severity_donut/use_severity_chart_data.ts | 4 +- 12 files changed, 28 insertions(+), 1233 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_by_severity_table.test.ts.snap delete mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap delete mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.test.ts delete mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts delete mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts delete mode 100644 x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index dd94d0c8d16e3c..bc1a247d1ad26b 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -26,9 +26,6 @@ import { InspectButtonContainer } from '../inspect'; import { DEFAULT_STACK_BY_FIELD0_SIZE, getAlertsRiskQuery } from '../alerts_treemap/query'; import type { AlertsTreeMapAggregation } from '../alerts_treemap/types'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; -import { getAlertsTreemapLensAttributes as getLensAttributes } from '../visualization_actions/lens_attributes/common/alerts/alerts_treemap'; -import { SourcererScopeName } from '../../store/sourcerer/model'; -import { VisualizationEmbeddable } from '../visualization_actions/visualization_embeddable'; const DEFAULT_HEIGHT = DEFAULT_MIN_CHART_HEIGHT + 134; // px @@ -92,15 +89,6 @@ const AlertsTreemapPanelComponent: React.FC = ({ // create a unique, but stable (across re-renders) query id const uniqueQueryId = useMemo(() => `${ALERTS_TREEMAP_ID}-${uuidv4()}`, []); const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); - const timerange = useMemo(() => ({ from, to }), [from, to]); - - const extraVisualizationOptions = useMemo( - () => ({ - breakdownField: stackByField1, - filters, - }), - [stackByField1, filters] - ); const additionalFilters = useMemo(() => { try { @@ -133,7 +121,7 @@ const AlertsTreemapPanelComponent: React.FC = ({ stackByField1, to, }), - skip: !isPanelExpanded || isChartEmbeddablesEnabled, + skip: !isPanelExpanded, indexName: signalIndexName, queryName: ALERTS_QUERY_NAMES.TREE_MAP, }); @@ -213,19 +201,7 @@ const AlertsTreemapPanelComponent: React.FC = ({ {isPanelExpanded ? ( - isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( - - ) : isLoadingAlerts ? ( + isLoadingAlerts ? ( ) : ( <> diff --git a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx index ea6c8444c0fada..8b968077f3dcb8 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx @@ -66,10 +66,10 @@ export interface DonutChartWrapperProps { /* Make this position absolute in order to overlap the text onto the donut */ export const DonutTextWrapper = styled(EuiFlexGroup)< EuiFlexGroupProps & { - $isChartEmbeddablesEnabled?: boolean; $dataExists?: boolean; + $donutTextWrapperStyles?: FlattenSimpleInterpolation; + $isChartEmbeddablesEnabled?: boolean; className?: string; - donutTextWrapperStyles?: FlattenSimpleInterpolation; } >` top: ${({ $isChartEmbeddablesEnabled, $dataExists }) => @@ -79,8 +79,8 @@ export const DonutTextWrapper = styled(EuiFlexGroup)< position: absolute; z-index: 1; - ${({ className, donutTextWrapperStyles }) => - className && donutTextWrapperStyles ? `&.${className} {${donutTextWrapperStyles}}` : ''} + ${({ className, $donutTextWrapperStyles }) => + className && $donutTextWrapperStyles ? `&.${className} {${$donutTextWrapperStyles}}` : ''} `; export const StyledEuiFlexItem = styled(EuiFlexItem)` @@ -116,11 +116,11 @@ const DonutChartWrapperComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_by_severity_table.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_by_severity_table.test.ts.snap deleted file mode 100644 index 3447332aaea962..00000000000000 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_by_severity_table.test.ts.snap +++ /dev/null @@ -1,161 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`getAlertsBySeverityTableAttributes should render without extra options 1`] = ` -Object { - "description": "", - "references": Array [ - Object { - "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-b9b43606-7ff7-46ae-a47c-85bed80fab9a", - "type": "index-pattern", - }, - Object { - "id": "security-solution-my-test", - "name": "22752b9b-cfcd-43f0-a6ee-27dd4893edcf", - "type": "index-pattern", - }, - ], - "state": Object { - "adHocDataViews": Object {}, - "datasourceStates": Object { - "formBased": Object { - "layers": Object { - "b9b43606-7ff7-46ae-a47c-85bed80fab9a": Object { - "columnOrder": Array [ - "a9b43606-7ff7-46ae-a47c-85bed80fab9a", - "21cc4a49-3780-4b1a-be28-f02fa5303d24", - ], - "columns": Object { - "21cc4a49-3780-4b1a-be28-f02fa5303d24": Object { - "dataType": "number", - "filter": Object { - "language": "kuery", - "query": "", - }, - "isBucketed": false, - "label": "Count of records", - "operationType": "count", - "params": Object { - "emptyAsNull": true, - }, - "scale": "ratio", - "sourceField": "___records___", - }, - "a9b43606-7ff7-46ae-a47c-85bed80fab9a": Object { - "dataType": "string", - "isBucketed": true, - "label": "Filters", - "operationType": "filters", - "params": Object { - "filters": Array [ - Object { - "input": Object { - "language": "kuery", - "query": "kibana.alert.severity: \\"critical\\"", - }, - "label": "Critical", - }, - Object { - "input": Object { - "language": "kuery", - "query": "kibana.alert.severity : \\"high\\"", - }, - "label": "High", - }, - Object { - "input": Object { - "language": "kuery", - "query": "kibana.alert.severity: \\"medium\\"", - }, - "label": "Medium", - }, - Object { - "input": Object { - "language": "kuery", - "query": "kibana.alert.severity : \\"low\\"", - }, - "label": "Low", - }, - ], - }, - "scale": "ordinal", - }, - }, - "incompleteColumns": Object {}, - "sampling": 1, - }, - }, - }, - "textBased": Object { - "layers": Object {}, - }, - }, - "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "host.id", - "negate": false, - "params": Object { - "query": "123", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "host.id": "123", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "_index", - "negate": false, - "params": Array [ - "signal-index", - ], - "type": "phrases", - }, - "query": Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "match_phrase": Object { - "_index": "signal-index", - }, - }, - ], - }, - }, - }, - ], - "internalReferences": Array [], - "query": Object { - "language": "kql", - "query": "host.name: *", - }, - "visualization": Object { - "columns": Array [ - Object { - "columnId": "a9b43606-7ff7-46ae-a47c-85bed80fab9a", - }, - Object { - "columnId": "21cc4a49-3780-4b1a-be28-f02fa5303d24", - }, - ], - "headerRowHeight": "custom", - "headerRowHeightLines": 0.6, - "layerId": "b9b43606-7ff7-46ae-a47c-85bed80fab9a", - "layerType": "data", - "rowHeight": "custom", - "rowHeightLines": 0.8, - }, - }, - "title": "Alerts", - "visualizationType": "lnsDatatable", -} -`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap deleted file mode 100644 index 981bb217aae6e2..00000000000000 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_treemap.test.ts.snap +++ /dev/null @@ -1,532 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`getAlertsTreemapLensAttributes should render with extra options - breakdownField 1`] = ` -Object { - "description": "", - "references": Array [ - Object { - "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "type": "index-pattern", - }, - ], - "state": Object { - "adHocDataViews": Object {}, - "datasourceStates": Object { - "formBased": Object { - "layers": Object { - "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { - "columnOrder": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "columns": Object { - "2881fedd-54b7-42ba-8c97-5175dec86166": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": "event.category", - }, - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of agent.type", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": "agent.type", - }, - "f04a71a3-399f-4d32-9efc-8a005e989991": Object { - "dataType": "number", - "isBucketed": false, - "label": "Maximum of kibana.alert.risk_score", - "operationType": "max", - "params": Object { - "emptyAsNull": true, - }, - "scale": "ratio", - "sourceField": "kibana.alert.risk_score", - }, - }, - "incompleteColumns": Object {}, - "sampling": 1, - }, - }, - }, - "textBased": Object { - "layers": Object {}, - }, - }, - "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "host.id", - "negate": false, - "params": Object { - "query": "123", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "host.id": "123", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "_index", - "negate": false, - "params": Array [ - "signal-index", - ], - "type": "phrases", - }, - "query": Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "match_phrase": Object { - "_index": "signal-index", - }, - }, - ], - }, - }, - }, - ], - "internalReferences": Array [], - "query": Object { - "language": "kql", - "query": "host.name: *", - }, - "visualization": Object { - "layers": Array [ - Object { - "categoryDisplay": "default", - "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "layerType": "data", - "legendDisplay": "show", - "legendPosition": "left", - "legendSize": "xlarge", - "metrics": Array [ - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "nestedLegend": true, - "numberDisplay": "value", - "primaryGroups": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - ], - }, - ], - "shape": "treemap", - }, - }, - "title": "Alerts", - "visualizationType": "lnsPie", -} -`; - -exports[`getAlertsTreemapLensAttributes should render with extra options - filters 1`] = ` -Object { - "description": "", - "references": Array [ - Object { - "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "type": "index-pattern", - }, - ], - "state": Object { - "adHocDataViews": Object {}, - "datasourceStates": Object { - "formBased": Object { - "layers": Object { - "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { - "columnOrder": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "columns": Object { - "2881fedd-54b7-42ba-8c97-5175dec86166": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": "event.category", - }, - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of undefined", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": undefined, - }, - "f04a71a3-399f-4d32-9efc-8a005e989991": Object { - "dataType": "number", - "isBucketed": false, - "label": "Maximum of kibana.alert.risk_score", - "operationType": "max", - "params": Object { - "emptyAsNull": true, - }, - "scale": "ratio", - "sourceField": "kibana.alert.risk_score", - }, - }, - "incompleteColumns": Object {}, - "sampling": 1, - }, - }, - }, - "textBased": Object { - "layers": Object {}, - }, - }, - "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "_index", - "negate": false, - "params": Array [ - ".alerts-security.alerts-default", - ], - "type": "phrases", - }, - "query": Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "match_phrase": Object { - "_index": ".alerts-security.alerts-default", - }, - }, - ], - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "host.id", - "negate": false, - "params": Object { - "query": "123", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "host.id": "123", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "_index", - "negate": false, - "params": Array [ - "signal-index", - ], - "type": "phrases", - }, - "query": Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "match_phrase": Object { - "_index": "signal-index", - }, - }, - ], - }, - }, - }, - ], - "internalReferences": Array [], - "query": Object { - "language": "kql", - "query": "host.name: *", - }, - "visualization": Object { - "layers": Array [ - Object { - "categoryDisplay": "default", - "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "layerType": "data", - "legendDisplay": "show", - "legendPosition": "left", - "legendSize": "xlarge", - "metrics": Array [ - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "nestedLegend": true, - "numberDisplay": "value", - "primaryGroups": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - ], - }, - ], - "shape": "treemap", - }, - }, - "title": "Alerts", - "visualizationType": "lnsPie", -} -`; - -exports[`getAlertsTreemapLensAttributes should render without extra options 1`] = ` -Object { - "description": "", - "references": Array [ - Object { - "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "type": "index-pattern", - }, - ], - "state": Object { - "adHocDataViews": Object {}, - "datasourceStates": Object { - "formBased": Object { - "layers": Object { - "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { - "columnOrder": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "columns": Object { - "2881fedd-54b7-42ba-8c97-5175dec86166": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": "event.category", - }, - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of undefined", - "operationType": "terms", - "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, - }, - "scale": "ordinal", - "sourceField": undefined, - }, - "f04a71a3-399f-4d32-9efc-8a005e989991": Object { - "dataType": "number", - "isBucketed": false, - "label": "Maximum of kibana.alert.risk_score", - "operationType": "max", - "params": Object { - "emptyAsNull": true, - }, - "scale": "ratio", - "sourceField": "kibana.alert.risk_score", - }, - }, - "incompleteColumns": Object {}, - "sampling": 1, - }, - }, - }, - "textBased": Object { - "layers": Object {}, - }, - }, - "filters": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "host.id", - "negate": false, - "params": Object { - "query": "123", - }, - "type": "phrase", - }, - "query": Object { - "match_phrase": Object { - "host.id": "123", - }, - }, - }, - Object { - "meta": Object { - "alias": null, - "disabled": false, - "key": "_index", - "negate": false, - "params": Array [ - "signal-index", - ], - "type": "phrases", - }, - "query": Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "match_phrase": Object { - "_index": "signal-index", - }, - }, - ], - }, - }, - }, - ], - "internalReferences": Array [], - "query": Object { - "language": "kql", - "query": "host.name: *", - }, - "visualization": Object { - "layers": Array [ - Object { - "categoryDisplay": "default", - "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", - "layerType": "data", - "legendDisplay": "show", - "legendPosition": "left", - "legendSize": "xlarge", - "metrics": Array [ - "f04a71a3-399f-4d32-9efc-8a005e989991", - ], - "nestedLegend": true, - "numberDisplay": "value", - "primaryGroups": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - ], - }, - ], - "shape": "treemap", - }, - }, - "title": "Alerts", - "visualizationType": "lnsPie", -} -`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.test.ts deleted file mode 100644 index 5c275979f6796d..00000000000000 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.test.ts +++ /dev/null @@ -1,63 +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 { renderHook } from '@testing-library/react-hooks'; -import { mockExtraFilter, wrapper } from '../../../mocks'; - -import { useLensAttributes } from '../../../use_lens_attributes'; - -import { getAlertsBySeverityTableAttributes } from './alerts_by_severity_table'; - -jest.mock('uuid', () => ({ - v4: jest.fn().mockReturnValue('b9b43606-7ff7-46ae-a47c-85bed80fab9a'), -})); - -jest.mock('../../../../../containers/sourcerer', () => ({ - useSourcererDataView: jest.fn().mockReturnValue({ - dataViewId: 'security-solution-my-test', - indicesExist: true, - selectedPatterns: ['signal-index'], - }), -})); - -jest.mock('../../../../../utils/route/use_route_spy', () => ({ - useRouteSpy: jest.fn().mockReturnValue([ - { - pageName: 'alerts', - }, - ]), -})); - -describe('getAlertsBySeverityTableAttributes', () => { - it('should render without extra options', () => { - const { result } = renderHook( - () => - useLensAttributes({ - getLensAttributes: getAlertsBySeverityTableAttributes, - stackByField: 'kibana.alert.severity', - }), - { wrapper } - ); - - expect(result?.current).toMatchSnapshot(); - }); - - it('should render with extra options - filters', () => { - const { result } = renderHook( - () => - useLensAttributes({ - extraOptions: { - filters: mockExtraFilter, - }, - getLensAttributes: getAlertsBySeverityTableAttributes, - stackByField: 'kibana.alert.severity', - }), - { wrapper } - ); - - expect(result?.current?.state.filters).toEqual(expect.arrayContaining(mockExtraFilter)); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts deleted file mode 100644 index ee71c07a4a26d3..00000000000000 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table.ts +++ /dev/null @@ -1,131 +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 type { GetLensAttributes } from '../../../types'; -const layerId = uuidv4(); - -export const getAlertsBySeverityTableAttributes: GetLensAttributes = ( - stackByField = 'kibana.alert.severity', - extraOptions -) => { - return { - title: 'Alerts', - description: '', - visualizationType: 'lnsDatatable', - state: { - visualization: { - layerId, - layerType: 'data', - columns: [ - { - columnId: 'a9b43606-7ff7-46ae-a47c-85bed80fab9a', - }, - { - columnId: '21cc4a49-3780-4b1a-be28-f02fa5303d24', - }, - ], - headerRowHeight: 'custom', - headerRowHeightLines: 0.6, - rowHeight: 'custom', - rowHeightLines: 0.8, - }, - query: { - query: '', - language: 'kuery', - }, - filters: extraOptions?.filters ? extraOptions.filters : [], - datasourceStates: { - formBased: { - layers: { - [layerId]: { - columns: { - 'a9b43606-7ff7-46ae-a47c-85bed80fab9a': { - label: 'Filters', - dataType: 'string', - operationType: 'filters', - scale: 'ordinal', - isBucketed: true, - params: { - filters: [ - { - input: { - query: `${stackByField}: "critical"`, - language: 'kuery', - }, - label: 'Critical', - }, - { - label: 'High', - input: { - query: `${stackByField} : "high"`, - language: 'kuery', - }, - }, - { - input: { - query: `${stackByField}: "medium"`, - language: 'kuery', - }, - label: 'Medium', - }, - { - input: { - query: `${stackByField} : "low"`, - language: 'kuery', - }, - label: 'Low', - }, - ], - }, - }, - '21cc4a49-3780-4b1a-be28-f02fa5303d24': { - label: 'Count of records', - dataType: 'number', - operationType: 'count', - isBucketed: false, - scale: 'ratio', - sourceField: '___records___', - filter: { - query: '', - language: 'kuery', - }, - params: { - emptyAsNull: true, - }, - }, - }, - columnOrder: [ - 'a9b43606-7ff7-46ae-a47c-85bed80fab9a', - '21cc4a49-3780-4b1a-be28-f02fa5303d24', - ], - sampling: 1, - incompleteColumns: {}, - }, - }, - }, - textBased: { - layers: {}, - }, - }, - internalReferences: [], - adHocDataViews: {}, - }, - references: [ - { - type: 'index-pattern', - id: '{dataViewId}', - name: `indexpattern-datasource-layer-${layerId}`, - }, - { - type: 'index-pattern', - name: '22752b9b-cfcd-43f0-a6ee-27dd4893edcf', - id: '{dataViewId}', - }, - ], - }; -}; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts deleted file mode 100644 index e09feb8ddb584f..00000000000000 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.test.ts +++ /dev/null @@ -1,100 +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 { renderHook } from '@testing-library/react-hooks'; -import { wrapper } from '../../../mocks'; - -import { useLensAttributes } from '../../../use_lens_attributes'; - -import { getAlertsTreemapLensAttributes } from './alerts_treemap'; - -jest.mock('uuid', () => ({ - v4: jest.fn().mockReturnValue('4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b'), -})); - -jest.mock('../../../../../containers/sourcerer', () => ({ - useSourcererDataView: jest.fn().mockReturnValue({ - dataViewId: 'security-solution-my-test', - indicesExist: true, - selectedPatterns: ['signal-index'], - }), -})); - -jest.mock('../../../../../utils/route/use_route_spy', () => ({ - useRouteSpy: jest.fn().mockReturnValue([ - { - pageName: 'alerts', - }, - ]), -})); - -describe('getAlertsTreemapLensAttributes', () => { - it('should render without extra options', () => { - const { result } = renderHook( - () => - useLensAttributes({ - getLensAttributes: getAlertsTreemapLensAttributes, - stackByField: 'event.category', - }), - { wrapper } - ); - - expect(result?.current).toMatchSnapshot(); - }); - - it('should render with extra options - filters', () => { - const { result } = renderHook( - () => - useLensAttributes({ - extraOptions: { - filters: [ - { - meta: { - type: 'phrases', - key: '_index', - params: ['.alerts-security.alerts-default'], - alias: null, - negate: false, - disabled: false, - }, - query: { - bool: { - should: [ - { - match_phrase: { - _index: '.alerts-security.alerts-default', - }, - }, - ], - minimum_should_match: 1, - }, - }, - }, - ], - }, - getLensAttributes: getAlertsTreemapLensAttributes, - stackByField: 'event.category', - }), - { wrapper } - ); - - expect(result?.current).toMatchSnapshot(); - }); - - it('should render with extra options - breakdownField', () => { - const { result } = renderHook( - () => - useLensAttributes({ - extraOptions: { breakdownField: 'agent.type' }, - getLensAttributes: getAlertsTreemapLensAttributes, - stackByField: 'event.category', - }), - { wrapper } - ); - - expect(result?.current).toMatchSnapshot(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts deleted file mode 100644 index dbf5d0c578f4df..00000000000000 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_treemap.ts +++ /dev/null @@ -1,138 +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 type { GetLensAttributes } from '../../../types'; - -const layerId = uuidv4(); - -export const getAlertsTreemapLensAttributes: GetLensAttributes = ( - stackByField = 'kibana.alert.rule.name', - extraOptions -) => { - return { - title: 'Alerts', - description: '', - visualizationType: 'lnsPie', - state: { - visualization: { - shape: 'treemap', - layers: [ - { - layerId, - primaryGroups: [ - '2881fedd-54b7-42ba-8c97-5175dec86166', - '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', - ], - metrics: ['f04a71a3-399f-4d32-9efc-8a005e989991'], - numberDisplay: 'value', - categoryDisplay: 'default', - legendDisplay: 'show', - nestedLegend: true, - layerType: 'data', - legendSize: 'xlarge', - legendPosition: 'left', - }, - ], - }, - query: { - query: '', - language: 'kuery', - }, - filters: extraOptions?.filters ? extraOptions.filters : [], - datasourceStates: { - formBased: { - layers: { - [layerId]: { - columns: { - '2881fedd-54b7-42ba-8c97-5175dec86166': { - label: `Top values of ${stackByField}`, - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: stackByField, - isBucketed: true, - params: { - size: 1000, - orderBy: { - type: 'column', - columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', - }, - orderDirection: 'desc', - otherBucket: true, - missingBucket: false, - parentFormat: { - id: 'terms', - }, - include: [], - exclude: [], - includeIsRegex: false, - excludeIsRegex: false, - }, - }, - '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { - label: `Top values of ${extraOptions?.breakdownField}`, - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: extraOptions?.breakdownField, - isBucketed: true, - params: { - size: 1000, - orderBy: { - type: 'column', - columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', - }, - orderDirection: 'desc', - otherBucket: true, - missingBucket: false, - parentFormat: { - id: 'terms', - }, - include: [], - exclude: [], - includeIsRegex: false, - excludeIsRegex: false, - }, - }, - 'f04a71a3-399f-4d32-9efc-8a005e989991': { - label: 'Maximum of kibana.alert.risk_score', - dataType: 'number', - operationType: 'max', - sourceField: 'kibana.alert.risk_score', - isBucketed: false, - scale: 'ratio', - params: { - emptyAsNull: true, - }, - }, - }, - columnOrder: [ - '2881fedd-54b7-42ba-8c97-5175dec86166', - '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', - 'f04a71a3-399f-4d32-9efc-8a005e989991', - ], - sampling: 1, - incompleteColumns: {}, - }, - }, - }, - textBased: { - layers: {}, - }, - }, - internalReferences: [], - adHocDataViews: {}, - }, - references: [ - { - type: 'index-pattern', - id: '{dataViewId}', - name: `indexpattern-datasource-layer-${layerId}`, - }, - ], - }; -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx index 0b31df3da4de8c..e7e51a9ebc4c54 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx @@ -16,7 +16,6 @@ import { HeaderSection } from '../../../../common/components/header_section'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { useSeverityChartData } from './severity_donut/use_severity_chart_data'; import { SeverityLevelChart } from './severity_donut/severity_level_chart'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; const DETECTIONS_ALERTS_CHARTS_ID = 'detections-alerts-charts'; @@ -55,7 +54,6 @@ export const AlertsSummaryChartsPanel: React.FC = ({ }: Props) => { // create a unique, but stable (across re-renders) query id const uniqueQueryId = useMemo(() => `${DETECTIONS_ALERTS_CHARTS_ID}-${uuidv4()}`, []); - const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const { toggleStatus, setToggleStatus } = useQueryToggle(DETECTIONS_ALERTS_CHARTS_ID); const [querySkip, setQuerySkip] = useState(!toggleStatus); @@ -71,16 +69,12 @@ export const AlertsSummaryChartsPanel: React.FC = ({ [setQuerySkip, setToggleStatus] ); - const { - items: severityData, - isLoading: isSeverityLoading, - timerange, - } = useSeverityChartData({ + const { items: severityData, isLoading: isSeverityLoading } = useSeverityChartData({ filters, query, signalIndexName, runtimeMappings, - skip: querySkip || isChartEmbeddablesEnabled, + skip: querySkip, uniqueQueryId, }); @@ -107,9 +101,7 @@ export const AlertsSummaryChartsPanel: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx index 17d31a3058e3a9..6868437e303e18 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx @@ -8,7 +8,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiInMemoryTable } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { isEmpty } from 'lodash/fp'; -import type { Filter } from '@kbn/es-query'; import { ALERT_SEVERITY } from '@kbn/rule-data-utils'; import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ShapeTreeNode, ElementClickListener } from '@elastic/charts'; @@ -22,10 +21,6 @@ import { InspectButtonContainer } from '../../../../../common/components/inspect import { getSeverityTableColumns } from '../columns'; import { getSeverityColor } from '../helpers'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; -import { getAlertsByStatusAttributes } from '../../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut'; -import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; -import { VisualizationEmbeddable } from '../../../../../common/components/visualization_actions/visualization_embeddable'; -import { getAlertsBySeverityTableAttributes } from '../../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_severity_table'; const DONUT_HEIGHT = 150; @@ -34,18 +29,14 @@ type FieldFilter = ({ field, value }: { field: string; value: string | number }) interface AlertsChartsPanelProps { addFilter?: FieldFilter; data: ParsedSeverityData; - filters?: Filter[]; isLoading: boolean; - timerange: { from: string; to: string }; uniqueQueryId: string; } export const SeverityLevelChart: React.FC = ({ addFilter, data, - filters, isLoading, - timerange, uniqueQueryId, }) => { const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); @@ -88,7 +79,6 @@ export const SeverityLevelChart: React.FC = ({ }, [addFilter] ); - const extraOptions = useMemo(() => ({ filters }), [filters]); return ( @@ -104,54 +94,25 @@ export const SeverityLevelChart: React.FC = ({ /> - {isChartEmbeddablesEnabled ? ( - - ) : ( - - )} + - {isChartEmbeddablesEnabled ? ( - - ) : ( - } - totalCount={count} - onElementClick={onElementClick} - /> - )} + } + totalCount={count} + onElementClick={onElementClick} + /> diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.test.tsx index 753f6715f923ba..0d48d023d06a34 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.test.tsx @@ -15,10 +15,7 @@ import { useSeverityChartData } from './use_severity_chart_data'; const dateNow = new Date('2022-04-08T12:00:00.000Z').valueOf(); const mockDateNow = jest.fn().mockReturnValue(dateNow); Date.now = jest.fn(() => mockDateNow()) as unknown as DateConstructor['now']; -const timerange = { - from: '2022-04-05T12:00:00.000Z', - to: '2022-04-08T12:00:00.000Z', -}; + const defaultUseQueryAlertsReturn = { loading: false, data: null, @@ -71,7 +68,6 @@ describe('useSeverityChartData', () => { items: null, isLoading: false, updatedAt: dateNow, - timerange, }); expect(mockUseQueryAlerts).toBeCalledWith({ @@ -93,7 +89,6 @@ describe('useSeverityChartData', () => { items: parsedAlerts, isLoading: false, updatedAt: dateNow, - timerange, }); }); @@ -114,7 +109,6 @@ describe('useSeverityChartData', () => { items: parsedAlerts, isLoading: false, updatedAt: newDateNow, - timerange, }); }); @@ -132,7 +126,6 @@ describe('useSeverityChartData', () => { items: null, isLoading: false, updatedAt: dateNow, - timerange, }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.ts index f8fef66bea3c05..32665399ce2ecd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/use_severity_chart_data.ts @@ -72,7 +72,6 @@ export interface UseSeverityChartProps { export type UseAlertsBySeverity = (props: UseSeverityChartProps) => { items: ParsedSeverityData; isLoading: boolean; - timerange: { from: string; to: string }; updatedAt: number; }; @@ -88,7 +87,6 @@ export const useSeverityChartData: UseAlertsBySeverity = ({ const { to, from, deleteQuery, setQuery } = useGlobalTime(); const [updatedAt, setUpdatedAt] = useState(Date.now()); const [items, setItems] = useState(null); - const timerange = useMemo(() => ({ to, from }), [to, from]); const additionalFilters = useMemo(() => { try { return [ @@ -160,5 +158,5 @@ export const useSeverityChartData: UseAlertsBySeverity = ({ uniqueQueryId, }); - return { items, isLoading, updatedAt, timerange }; + return { items, isLoading, updatedAt }; }; From 23878fa41b2461b3290c122a181d7b4ac71a6d73 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Jan 2023 13:28:26 +0000 Subject: [PATCH 54/72] revert change --- .../components/alerts_treemap_panel/index.tsx | 36 +++++++++---------- .../pages/rule_details/index.tsx | 4 +-- .../alerts_summary_charts_panel/index.tsx | 2 +- .../severity_level_chart.test.tsx | 4 --- .../severity_donut/severity_level_chart.tsx | 19 ++++------ .../severity_donut/use_severity_chart_data.ts | 1 + 6 files changed, 27 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index bc1a247d1ad26b..07c3137359c80f 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -183,9 +183,7 @@ const AlertsTreemapPanelComponent: React.FC = ({ > {isPanelExpanded && ( = ({ )} - {isPanelExpanded ? ( - isLoadingAlerts ? ( - - ) : ( - <> - {alertsData != null && isPanelExpanded && ( - - )} - - ) - ) : null} + {isLoadingAlerts && isPanelExpanded ? ( + + ) : ( + <> + {alertsData != null && isPanelExpanded && ( + + )} + + )} ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index 2648804a934933..f9638456e68144 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -827,12 +827,12 @@ const RuleDetailsPageComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx index e7e51a9ebc4c54..b49a91069fce2d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx @@ -99,10 +99,10 @@ export const AlertsSummaryChartsPanel: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.test.tsx index bf725f57b69851..3b6b262efb545a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.test.tsx @@ -21,10 +21,6 @@ describe('Severity level chart', () => { data: [], isLoading: false, uniqueQueryId: 'test-query-id', - timerange: { - from: '2023-01-10T00:00:00.000Z', - to: '2023-01-10T23:59:59.999Z', - }, }; afterEach(() => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx index 6868437e303e18..1183c66735f29c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/severity_donut/severity_level_chart.tsx @@ -20,26 +20,22 @@ import { HeaderSection } from '../../../../../common/components/header_section'; import { InspectButtonContainer } from '../../../../../common/components/inspect'; import { getSeverityTableColumns } from '../columns'; import { getSeverityColor } from '../helpers'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; const DONUT_HEIGHT = 150; -type FieldFilter = ({ field, value }: { field: string; value: string | number }) => void; - interface AlertsChartsPanelProps { - addFilter?: FieldFilter; data: ParsedSeverityData; isLoading: boolean; uniqueQueryId: string; + addFilter?: ({ field, value }: { field: string; value: string | number }) => void; } export const SeverityLevelChart: React.FC = ({ - addFilter, data, isLoading, uniqueQueryId, + addFilter, }) => { - const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const fillColor: FillColor = useCallback((d: ShapeTreeNode) => { return getSeverityColor(d.dataName); }, []); @@ -62,7 +58,6 @@ export const SeverityLevelChart: React.FC = ({ }, }; - const style = isChartEmbeddablesEnabled ? { width: '50%' } : undefined; const onElementClick: ElementClickListener = useCallback( (event) => { const flattened = event.flat(2); @@ -79,21 +74,21 @@ export const SeverityLevelChart: React.FC = ({ }, [addFilter] ); + return ( - - + + = ({ sorting={sorting} /> - + (null); + const additionalFilters = useMemo(() => { try { return [ From 47ae44b0c9a1e54a9e168f42fc6a7cd284eeee17 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Jan 2023 14:01:27 +0000 Subject: [PATCH 55/72] revert changes --- .../components/alerts_treemap_panel/index.tsx | 15 +++++---------- .../pages/detection_engine/chart_panels/index.tsx | 1 - 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index 07c3137359c80f..62113a5367bbb2 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -6,7 +6,6 @@ */ import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; -import type { Action } from '@kbn/ui-actions-plugin/public'; import type { EuiComboBox } from '@elastic/eui'; import { EuiProgress } from '@elastic/eui'; import type { Filter, Query } from '@kbn/es-query'; @@ -25,7 +24,6 @@ import { HeaderSection } from '../header_section'; import { InspectButtonContainer } from '../inspect'; import { DEFAULT_STACK_BY_FIELD0_SIZE, getAlertsRiskQuery } from '../alerts_treemap/query'; import type { AlertsTreeMapAggregation } from '../alerts_treemap/types'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; const DEFAULT_HEIGHT = DEFAULT_MIN_CHART_HEIGHT + 134; // px @@ -37,11 +35,10 @@ export interface Props { addFilter?: ({ field, value }: { field: string; value: string | number }) => void; alignHeader?: 'center' | 'baseline' | 'stretch' | 'flexStart' | 'flexEnd'; chartOptionsContextMenu?: (queryId: string) => React.ReactNode; - extraActions?: Action[]; - filters?: Filter[]; - height?: number; inspectTitle: string; isPanelExpanded: boolean; + filters?: Filter[]; + height?: number; query?: Query; riskSubAggregationField: string; runtimeMappings?: MappingRuntimeFields; @@ -63,11 +60,10 @@ const AlertsTreemapPanelComponent: React.FC = ({ addFilter, alignHeader, chartOptionsContextMenu, - extraActions, - filters, - height = DEFAULT_HEIGHT, inspectTitle, isPanelExpanded, + filters, + height = DEFAULT_HEIGHT, query, riskSubAggregationField, runtimeMappings, @@ -88,7 +84,6 @@ const AlertsTreemapPanelComponent: React.FC = ({ // create a unique, but stable (across re-renders) query id const uniqueQueryId = useMemo(() => `${ALERTS_TREEMAP_ID}-${uuidv4()}`, []); - const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled'); const additionalFilters = useMemo(() => { try { @@ -152,9 +147,9 @@ const AlertsTreemapPanelComponent: React.FC = ({ useInspectButton({ deleteQuery, loading: isLoadingAlerts, - refetch, request, response, + refetch, setQuery, uniqueQueryId, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx index ce4ccfcbed7c48..d2860319095557 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/chart_panels/index.tsx @@ -242,7 +242,6 @@ const ChartPanelsComponent: React.FC = ({ addFilter={addFilter} alignHeader="flexStart" chartOptionsContextMenu={chartOptionsContextMenu} - extraActions={resetGroupByFieldAction} filters={alertsHistogramDefaultFilters} inspectTitle={i18n.TREEMAP} isPanelExpanded={isTreemapPanelExpanded} From d75d3e73fb9979565f3bf396c5fc7efbbe33730b Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Jan 2023 14:04:36 +0000 Subject: [PATCH 56/72] revert changes --- .../public/common/components/alerts_treemap_panel/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx index 62113a5367bbb2..a3ba582b3c974a 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.tsx @@ -147,10 +147,10 @@ const AlertsTreemapPanelComponent: React.FC = ({ useInspectButton({ deleteQuery, loading: isLoadingAlerts, - request, response, - refetch, setQuery, + refetch, + request, uniqueQueryId, }); From 73c559c8c714a88b9589ced0859261647052028b Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Jan 2023 15:33:50 +0000 Subject: [PATCH 57/72] revert changes --- .../alerts_treemap_panel/index.test.tsx | 59 ------------------- 1 file changed, 59 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx index d843866233dd60..0db21ee27ea75d 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_treemap_panel/index.test.tsx @@ -23,7 +23,6 @@ import { TestProviders } from '../../mock/test_providers'; import type { Props } from '.'; import { AlertsTreemapPanel } from '.'; import { mockAlertSearchResponse } from '../alerts_treemap/lib/mocks/mock_alert_search_response'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; const from = '2022-07-28T08:20:18.966Z'; const to = '2022-07-28T08:20:18.966Z'; @@ -54,15 +53,6 @@ jest.mock('../../../detections/containers/detection_engine/alerts/use_query', () useQueryAlerts: jest.fn(), })); -jest.mock('../../hooks/use_experimental_features'); -jest.mock('../page/use_refetch_by_session'); -jest.mock('../visualization_actions/lens_embeddable'); - -jest.mock('../../../detections/components/alerts_kpis/common/hooks', () => ({ - useInspectButton: jest.fn(), - useStackByFields: jest.fn(), -})); - const defaultProps: Props = { addFilter: jest.fn(), alignHeader: 'flexStart', @@ -149,8 +139,6 @@ describe('AlertsTreemapPanel', () => { request: '', refetch: () => {}, }); - - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); }); it('renders the panel', async () => { @@ -320,50 +308,3 @@ describe('AlertsTreemapPanel', () => { await waitFor(() => expect(screen.getByTestId('treemap')).toBeInTheDocument()); }); }); - -describe('when isChartEmbeddablesEnabled = true', () => { - const mockRefetch = () => {}; - - beforeEach(() => { - jest.clearAllMocks(); - - (useLocation as jest.Mock).mockReturnValue([ - { pageName: SecurityPageName.alerts, detailName: undefined }, - ]); - - (useQueryAlerts as jest.Mock).mockReturnValue({ - loading: false, - data: mockAlertSearchResponse, - setQuery: () => {}, - response: '', - request: '', - refetch: mockRefetch, - }); - - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); - }); - - it('renders LensEmbeddable', async () => { - render( - - - - ); - - expect(screen.getByTestId('lens-embeddable')).toBeInTheDocument(); - }); - - it('should skip calling getAlertsRiskQuery', async () => { - render( - - - - ); - - expect(useQueryAlerts).toHaveBeenCalledWith( - expect.objectContaining({ - skip: true, - }) - ); - }); -}); From d81d136b72ab952d05f15f29d286aad577e3ab91 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 27 Jan 2023 19:37:49 +0000 Subject: [PATCH 58/72] page crashes when deleting group by field --- .../use_lens_attributes.test.tsx | 18 ++++++++++++++++++ .../use_lens_attributes.tsx | 5 ++++- .../visualization_embeddable.tsx | 4 ++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx index 7451209120e066..2a39c634e6a725 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx @@ -205,6 +205,24 @@ describe('useLensAttributes', () => { expect(result?.current).toBeNull(); }); + it('should return null if stackByField is an empty string', () => { + (useSourcererDataView as jest.Mock).mockReturnValue({ + dataViewId: 'security-solution-default', + indicesExist: false, + selectedPatterns: ['auditbeat-*'], + }); + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getExternalAlertLensAttributes, + stackByField: '', + }), + { wrapper } + ); + + expect(result?.current).toBeNull(); + }); + it('should return Lens attributes if adHocDataViews exist', () => { (useSourcererDataView as jest.Mock).mockReturnValue({ dataViewId: 'security-solution-default', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index 1976a743e5fa16..d85b38acd0b3e1 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -89,7 +89,10 @@ export const useLensAttributes = ({ const hasAdHocDataViews = Object.values(attrs?.state?.adHocDataViews ?? {}).length > 0; const lensAttrsWithInjectedData = useMemo(() => { - if (lensAttributes == null && (getLensAttributes == null || stackByField == null)) { + if ( + lensAttributes == null && + (getLensAttributes == null || stackByField == null || stackByField?.length === 0) + ) { return null; } diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx index 7ec7c9ee168ac2..743e904ffca46b 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx @@ -39,7 +39,7 @@ const VisualizationEmbeddableComponent: React.FC = const visualizationData = inspect?.response ? parseVisualizationData(inspect?.response) : null; - const dataExists = visualizationData != null && visualizationData[0]?.hits.total !== 0; + const dataExists = visualizationData != null && visualizationData[0]?.hits?.total !== 0; const donutTextWrapperStyles = dataExists ? css` top: 40%; @@ -94,7 +94,7 @@ const VisualizationEmbeddableComponent: React.FC = isChartEmbeddablesEnabled={true} dataExists={dataExists} label={label} - title={dataExists ? : null} + title={dataExists ? : null} donutTextWrapperClassName={donutTextWrapperClassName} donutTextWrapperStyles={donutTextWrapperStyles} > From 9ae3379404515bbd1f6b5bd65ce0cb791c61678d Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 30 Jan 2023 12:57:50 +0000 Subject: [PATCH 59/72] check estypes for stackByFields --- .../use_lens_attributes.test.tsx | 21 +++++++++++++++++++ .../use_lens_attributes.tsx | 6 +++++- .../components/alerts_kpis/common/hooks.ts | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx index 2a39c634e6a725..6fd82d4b0e1e9c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx @@ -223,6 +223,27 @@ describe('useLensAttributes', () => { expect(result?.current).toBeNull(); }); + it('should return null if extraOptions.breakDownField is an empty string', () => { + (useSourcererDataView as jest.Mock).mockReturnValue({ + dataViewId: 'security-solution-default', + indicesExist: false, + selectedPatterns: ['auditbeat-*'], + }); + const { result } = renderHook( + () => + useLensAttributes({ + getLensAttributes: getExternalAlertLensAttributes, + stackByField: 'kibana.alert.rule.name', + extraOptions: { + breakdownField: '', + }, + }), + { wrapper } + ); + + expect(result?.current).toBeNull(); + }); + it('should return Lens attributes if adHocDataViews exist', () => { (useSourcererDataView as jest.Mock).mockReturnValue({ dataViewId: 'security-solution-default', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index d85b38acd0b3e1..9b5ef16dddd22b 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -91,7 +91,10 @@ export const useLensAttributes = ({ const lensAttrsWithInjectedData = useMemo(() => { if ( lensAttributes == null && - (getLensAttributes == null || stackByField == null || stackByField?.length === 0) + (getLensAttributes == null || + stackByField == null || + stackByField?.length === 0 || + (extraOptions?.breakdownField != null && extraOptions?.breakdownField.length === 0)) ) { return null; } @@ -120,6 +123,7 @@ export const useLensAttributes = ({ applyGlobalQueriesAndFilters, attrs, dataViewId, + extraOptions?.breakdownField, filters, getLensAttributes, hasAdHocDataViews, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts index 079b69b76e4885..5572933f6b424f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts @@ -63,7 +63,7 @@ export function getAggregatableFields(fields: { }): EuiComboBoxOptionOption[] { const result = []; for (const [key, field] of Object.entries(fields)) { - if (field.aggregatable === true) { + if (field.aggregatable === true && field.esTypes && field.esTypes?.indexOf('keyword') >= 0) { result.push({ label: key, value: key }); } } From 7f215e41ba137035cdfbc651972991258285f51d Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 30 Jan 2023 15:37:12 +0000 Subject: [PATCH 60/72] use extra large space for alerts trend legend --- .../common/alerts/__snapshots__/alerts_histogram.test.ts.snap | 2 ++ .../lens_attributes/common/alerts/alerts_histogram.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap index 05da68bed2a0ee..d0b6f7a79ce340 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap @@ -163,6 +163,7 @@ Object { ], "legend": Object { "isVisible": true, + "legendSize": "xlarge", "position": "left", }, "preferredSeriesType": "bar_stacked", @@ -321,6 +322,7 @@ Object { ], "legend": Object { "isVisible": true, + "legendSize": "xlarge", "position": "left", }, "preferredSeriesType": "bar_stacked", diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts index 2de1ff0abadbf9..78b4a134a76205 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts @@ -22,6 +22,7 @@ export const getAlertsHistogramLensAttributes: GetLensAttributes = ( legend: { isVisible: true, position: 'left', + legendSize: 'xlarge', }, valueLabels: 'hide', preferredSeriesType: 'bar_stacked', From 8404d7be62fc3b59dcb7d28303dfae9f9e4b29b1 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 31 Jan 2023 17:14:33 +0000 Subject: [PATCH 61/72] refetch --- .../page/use_refetch_by_session.tsx | 9 ++++ .../visualization_actions/lens_embeddable.tsx | 4 ++ .../visualization_embeddable.tsx | 46 ++++++++++++------- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx b/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx index 930934fb177bad..48c66198de0534 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx @@ -29,6 +29,7 @@ export const useRefetchByRestartingSession = ({ }: UseRefetchByRestartingSessionProps): { session: MutableRefObject; refetchByRestartingSession: Refetch; + refetchByDeletingSession: Refetch; } => { const dispatch = useDispatch(); const { data } = useKibana().services; @@ -59,8 +60,16 @@ export const useRefetchByRestartingSession = ({ ); }, [dispatch, queryId, selectedInspectIndex, skip]); + /** + * This is for refetching alert index when the first rule just created + */ + const refetchByDeletingSession = useCallback(() => { + dispatch(inputsActions.deleteOneQuery({ inputId: InputsModelId.global, id: queryId })); + }, [dispatch, queryId]); + return { session, refetchByRestartingSession, + refetchByDeletingSession, }; }; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index ae76d049e99ff2..4064e5cb0d466a 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -172,6 +172,10 @@ const LensEmbeddableComponent: React.FC = ({ [attributes?.state?.adHocDataViews] ); + if (!searchSessionId) { + return null; + } + if ( !attributes || (visualizationData?.responses != null && visualizationData?.responses?.length === 0) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx index 743e904ffca46b..eeccb86931cbfc 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx @@ -30,12 +30,13 @@ const VisualizationEmbeddableComponent: React.FC = onLoad, ...lensPorps } = props; - const { session, refetchByRestartingSession } = useRefetchByRestartingSession({ - inputId, - queryId: id, - }); + const { session, refetchByRestartingSession, refetchByDeletingSession } = + useRefetchByRestartingSession({ + inputId, + queryId: id, + }); const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); - const { inspect } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); + const { inspect, searchSessionId } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); const visualizationData = inspect?.response ? parseVisualizationData(inspect?.response) : null; @@ -70,17 +71,30 @@ const VisualizationEmbeddableComponent: React.FC = ); useEffect(() => { - dispatch( - inputsActions.setQuery({ - inputId, - id, - searchSessionId: session.current.start(), - refetch: refetchByRestartingSession, - loading: false, - inspect: null, - }) - ); - }, [dispatch, inputId, id, refetchByRestartingSession, session]); + if (!searchSessionId) { + setTimeout(() => { + dispatch( + inputsActions.setQuery({ + inputId, + id, + searchSessionId: session.current.start(), + refetch: dataExists ? refetchByRestartingSession : refetchByDeletingSession, + loading: false, + inspect: null, + }) + ); + }, 200); + } + }, [ + dispatch, + inputId, + id, + session, + dataExists, + refetchByRestartingSession, + searchSessionId, + refetchByDeletingSession, + ]); useEffect(() => { return () => { From ef18579e05cbfd824df182a24405d38afebcaba1 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 31 Jan 2023 21:18:26 +0000 Subject: [PATCH 62/72] unit test --- .../visualization_embeddable.test.tsx | 93 ++++++++++++++++--- 1 file changed, 82 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx index c53835c0f86b95..ee77776e60a91b 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import type { RenderResult } from '@testing-library/react'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { kpiHostMetricLensAttributes } from './lens_attributes/hosts/kpi_host_metric'; import { VisualizationEmbeddable } from './visualization_embeddable'; import * as inputActions from '../../store/inputs/actions'; @@ -29,11 +29,12 @@ jest.mock('./lens_embeddable'); jest.mock('../page/use_refetch_by_session', () => ({ useRefetchByRestartingSession: jest.fn(), })); - +jest.useFakeTimers(); let res: RenderResult; const mockSearchSessionId = 'mockSearchSessionId'; const mockSearchSessionIdDefault = 'mockSearchSessionIdDefault'; const mockRefetchByRestartingSession = jest.fn(); +const mockRefetchByDeletingSession = jest.fn(); const mockSetQuery = jest.spyOn(inputActions, 'setQuery'); const mockDeleteQuery = jest.spyOn(inputActions, 'deleteOneQuery'); const state: State = { @@ -41,6 +42,7 @@ const state: State = { }; const { storage } = createSecuritySolutionStorageMock(); const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + describe('VisualizationEmbeddable', () => { describe('when isDonut = false', () => { beforeEach(() => { @@ -55,6 +57,7 @@ describe('VisualizationEmbeddable', () => { }, }, refetchByRestartingSession: mockRefetchByRestartingSession, + refetchByDeletingSession: mockRefetchByDeletingSession, }); res = render( @@ -71,15 +74,16 @@ describe('VisualizationEmbeddable', () => { expect(res.getByTestId('lens-embeddable')).toBeInTheDocument(); }); - it('should set query', () => { - expect(mockSetQuery).toHaveBeenCalledTimes(1); - expect(mockSetQuery).toHaveBeenCalledWith({ - inputId: InputsModelId.global, - id: 'testId', - searchSessionId: mockSearchSessionId, - refetch: mockRefetchByRestartingSession, - loading: false, - inspect: null, + it('should refetch by delete session when no data exists', async () => { + await waitFor(() => { + expect(mockSetQuery).toHaveBeenCalledWith({ + inputId: InputsModelId.global, + id: 'testId', + searchSessionId: mockSearchSessionId, + refetch: mockRefetchByDeletingSession, + loading: false, + inspect: null, + }); }); }); @@ -92,6 +96,73 @@ describe('VisualizationEmbeddable', () => { }); }); + describe('when data exists', () => { + const mockState = { + ...mockGlobalState, + inputs: { + ...mockGlobalState.inputs, + global: { + ...mockGlobalState.inputs.global, + queries: [ + { + id: 'testId', + inspect: { + dsl: [], + response: [ + '{\n "took": 4,\n "timed_out": false,\n "_shards": {\n "total": 3,\n "successful": 3,\n "skipped": 2,\n "failed": 0\n },\n "hits": {\n "total": 21300,\n "max_score": null,\n "hits": []\n },\n "aggregations": {\n "0": {\n "buckets": {\n "Critical": {\n "doc_count": 0\n },\n "High": {\n "doc_count": 0\n },\n "Low": {\n "doc_count": 21300\n },\n "Medium": {\n "doc_count": 0\n }\n }\n }\n }\n}', + ], + }, + isInspected: false, + loading: false, + selectedInspectIndex: 0, + searchSessionId: undefined, + refetch: jest.fn(), + }, + ], + }, + }, + }; + const mockStore = createStore(mockState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); + + beforeEach(() => { + jest.clearAllMocks(); + (useRefetchByRestartingSession as jest.Mock).mockReturnValue({ + session: { + current: { + start: jest + .fn() + .mockReturnValueOnce(mockSearchSessionId) + .mockReturnValue(mockSearchSessionIdDefault), + }, + }, + refetchByRestartingSession: mockRefetchByRestartingSession, + refetchByDeletingSession: mockRefetchByDeletingSession, + }); + res = render( + + + + ); + }); + + it('should refetch by restart session', async () => { + await waitFor(() => { + expect(mockSetQuery).toHaveBeenCalledWith({ + inputId: InputsModelId.global, + id: 'testId', + searchSessionId: mockSearchSessionId, + refetch: mockRefetchByRestartingSession, + loading: false, + inspect: null, + }); + }); + }); + }); + describe('when isDonut = true', () => { beforeEach(() => { jest.clearAllMocks(); From f5d9e5a941c78c01b2778e9cee757595303aa3d2 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 1 Feb 2023 10:53:20 +0000 Subject: [PATCH 63/72] review --- .../public/common/components/page/use_refetch_by_session.tsx | 3 ++- .../components/visualization_actions/lens_embeddable.tsx | 3 +-- .../visualization_actions/visualization_embeddable.tsx | 1 + .../components/alerts_kpis/alerts_count_panel/index.tsx | 4 +++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx b/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx index 48c66198de0534..85ff4940f37b67 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/use_refetch_by_session.tsx @@ -45,6 +45,7 @@ export const useRefetchByRestartingSession = ({ ); const refetchByRestartingSession = useCallback(() => { + const searchSessionId = session.current.start(); dispatch( inputsActions.setInspectionParameter({ id: queryId, @@ -55,7 +56,7 @@ export const useRefetchByRestartingSession = ({ * like most of our components, it refetches when receiving a new search * session ID. **/ - searchSessionId: skip ? undefined : session.current.start(), + searchSessionId: skip ? undefined : searchSessionId, }) ); }, [dispatch, queryId, selectedInspectIndex, skip]); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 4064e5cb0d466a..19805b8ce96f33 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -31,7 +31,7 @@ const LensComponentWrapper = styled.div<{ height?: string; width?: string }>` background-color: transparent; } .expExpressionRenderer__expression { - padding: 0 !important; + padding: 2px 0 0 0 !important; } .legacyMtrVis__container { padding: 0; @@ -88,7 +88,6 @@ const LensEmbeddableComponent: React.FC = ({ stackByField, title: '', }); - const LensComponent = lens.EmbeddableComponent; const inspectActionProps = useMemo( () => ({ diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx index eeccb86931cbfc..dda3e4a8696cb6 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx @@ -71,6 +71,7 @@ const VisualizationEmbeddableComponent: React.FC = ); useEffect(() => { + // This handles refetch when (alert) indices not found if (!searchSessionId) { setTimeout(() => { dispatch( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index 769a6720bc76fd..1da317a419a31d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -147,6 +147,8 @@ export const AlertsCountPanel = memo( queryName: ALERTS_QUERY_NAMES.COUNT, }); + const showVisualizationEmbeddable = isChartEmbeddablesEnabled && getLensAttributes && timerange; + useEffect(() => { setAlertsQuery( getAlertsCountQuery({ @@ -215,7 +217,7 @@ export const AlertsCountPanel = memo( /> {toggleStatus ? ( - isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( + showVisualizationEmbeddable ? ( Date: Wed, 1 Feb 2023 13:39:52 +0000 Subject: [PATCH 64/72] update event histogram's configs --- .../lens_attributes/common/__snapshots__/event.test.ts.snap | 1 + .../visualization_actions/lens_attributes/common/events.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap index c3a84cdcea2c6b..ab1eed774e8aa2 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap @@ -175,6 +175,7 @@ Object { ], "legend": Object { "isVisible": true, + "legendSize": "xlarge", "position": "left", }, "preferredSeriesType": "bar_stacked", diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts index e43a0f3d260110..61e9bac0cb3ac5 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts @@ -22,6 +22,7 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = ( legend: { isVisible: true, position: 'left', + legendSize: 'xlarge', }, valueLabels: 'hide', preferredSeriesType: 'bar_stacked', From 151a317f6fe72a3ab8f0dd2f24a8ae57f05af506 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 1 Feb 2023 13:44:59 +0000 Subject: [PATCH 65/72] typo --- .../visualization_actions/visualization_embeddable.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx index dda3e4a8696cb6..998ec2fbebcee5 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import { useDispatch } from 'react-redux'; import { css } from 'styled-components'; import { ChartLabel } from '../../../overview/components/detection_response/alerts_by_status/chart_label'; @@ -28,7 +28,7 @@ const VisualizationEmbeddableComponent: React.FC = label, donutTextWrapperClassName, onLoad, - ...lensPorps + ...lensProps } = props; const { session, refetchByRestartingSession, refetchByDeletingSession } = useRefetchByRestartingSession({ @@ -113,12 +113,12 @@ const VisualizationEmbeddableComponent: React.FC = donutTextWrapperClassName={donutTextWrapperClassName} donutTextWrapperStyles={donutTextWrapperStyles} > - + ); } - return ; + return ; }; export const VisualizationEmbeddable = React.memo(VisualizationEmbeddableComponent); From f6fecc3301066b52a3fc910aac40b44405c7ebed Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 1 Feb 2023 13:45:52 +0000 Subject: [PATCH 66/72] lint --- .../visualization_actions/visualization_embeddable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx index 998ec2fbebcee5..39e106c37e4650 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect, useRef } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { css } from 'styled-components'; import { ChartLabel } from '../../../overview/components/detection_response/alerts_by_status/chart_label'; From e949a18e4399876354f6206a4359a596066eb069 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 1 Feb 2023 15:26:59 +0000 Subject: [PATCH 67/72] handle timerange update when indices nout found --- .../visualization_embeddable.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx index 39e106c37e4650..1a9f78eef2aa7c 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import { useDispatch } from 'react-redux'; import { css } from 'styled-components'; import { ChartLabel } from '../../../overview/components/detection_response/alerts_by_status/chart_label'; @@ -18,6 +18,7 @@ import { InputsModelId } from '../../store/inputs/constants'; import { useRefetchByRestartingSession } from '../page/use_refetch_by_session'; import { LensEmbeddable } from './lens_embeddable'; import type { EmbeddableData, VisualizationEmbeddableProps } from './types'; +import { useSourcererDataView } from '../../containers/sourcerer'; const VisualizationEmbeddableComponent: React.FC = (props) => { const dispatch = useDispatch(); @@ -35,6 +36,9 @@ const VisualizationEmbeddableComponent: React.FC = inputId, queryId: id, }); + const { indicesExist } = useSourcererDataView(lensProps.scopeId); + + const memorizedTimerange = useRef(lensProps.timerange); const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); const { inspect, searchSessionId } = useDeepEqualSelector((state) => getGlobalQuery(state, id)); const visualizationData = inspect?.response @@ -71,7 +75,18 @@ const VisualizationEmbeddableComponent: React.FC = ); useEffect(() => { - // This handles refetch when (alert) indices not found + // This handles timerange update when (alert) indices not found + if ( + (!indicesExist && memorizedTimerange.current?.from !== lensProps.timerange.from) || + memorizedTimerange.current?.to !== lensProps.timerange.to + ) { + memorizedTimerange.current = lensProps.timerange; + dispatch(inputsActions.deleteOneQuery({ inputId, id })); + } + }, [dispatch, id, indicesExist, inputId, lensProps.timerange]); + + useEffect(() => { + // This handles initial mount and refetch when (alert) indices not found if (!searchSessionId) { setTimeout(() => { dispatch( From ea59f991b410fe8bed25e818db7668363c718c4e Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 1 Feb 2023 16:21:29 +0000 Subject: [PATCH 68/72] clear sessions before leaving security --- x-pack/plugins/security_solution/public/app/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/app/index.tsx b/x-pack/plugins/security_solution/public/app/index.tsx index 98b82a8d5b8fa9..29abcc5c475ea5 100644 --- a/x-pack/plugins/security_solution/public/app/index.tsx +++ b/x-pack/plugins/security_solution/public/app/index.tsx @@ -49,5 +49,8 @@ export const renderApp = ({ , element ); - return () => unmountComponentAtNode(element); + return () => { + services.data.search.session.clear(); + unmountComponentAtNode(element); + }; }; From 913d47053328757cb80a5cda349dcfc73bbf6c28 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 1 Feb 2023 16:40:30 +0000 Subject: [PATCH 69/72] code review --- .../public/common/components/matrix_histogram/index.tsx | 2 +- .../visualization_actions/visualization_embeddable.tsx | 4 ++++ .../components/alerts_kpis/alerts_count_panel/index.tsx | 4 +--- .../components/alerts_kpis/alerts_histogram_panel/index.tsx | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index 63a8158423193b..5a3a281d72d159 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -300,7 +300,7 @@ export const MatrixHistogramComponent: React.FC = {toggleStatus ? ( - isChartEmbeddablesEnabled && (getLensAttributes || lensAttributes) && timerange ? ( + isChartEmbeddablesEnabled ? ( = }; }, [dispatch, id, inputId]); + if ((!lensProps.getLensAttributes && !lensProps.lensAttributes) || !lensProps.timerange) { + return null; + } + if (isDonut) { return ( ( queryName: ALERTS_QUERY_NAMES.COUNT, }); - const showVisualizationEmbeddable = isChartEmbeddablesEnabled && getLensAttributes && timerange; - useEffect(() => { setAlertsQuery( getAlertsCountQuery({ @@ -217,7 +215,7 @@ export const AlertsCountPanel = memo( /> {toggleStatus ? ( - showVisualizationEmbeddable ? ( + isChartEmbeddablesEnabled ? ( ( {toggleStatus ? ( - isChartEmbeddablesEnabled && getLensAttributes && timerange ? ( + isChartEmbeddablesEnabled ? ( Date: Wed, 1 Feb 2023 17:34:59 +0000 Subject: [PATCH 70/72] put back missing props --- .../public/common/components/matrix_histogram/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index 5a3a281d72d159..48e812ff2afa3e 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -307,6 +307,7 @@ export const MatrixHistogramComponent: React.FC = height={CHART_HEIGHT} id={`${id}-embeddable`} inspectTitle={title as string} + lensAttributes={lensAttributes} stackByField={selectedStackByOption.value} timerange={timerange} /> From 11f18e873370b8f347d0055fca5609bba1896331 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 2 Feb 2023 13:05:14 +0000 Subject: [PATCH 71/72] block nested types --- .../alerts_count_panel/alerts_count.tsx | 2 - .../alerts_count_panel/chart_content.tsx | 61 +++++++++++++++++++ .../alerts_kpis/alerts_count_panel/index.tsx | 41 ++++++------- .../components/alerts_kpis/common/hooks.ts | 13 +++- 4 files changed, 91 insertions(+), 26 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/chart_content.tsx diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.tsx index 4f538b64b31eed..e944e496603fea 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.tsx @@ -12,8 +12,6 @@ import styled from 'styled-components'; import { useUiSetting$ } from '../../../../common/lib/kibana'; import { DEFAULT_NUMBER_FORMAT } from '../../../../../common/constants'; -import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; -import type { AlertsCountAggregation } from './types'; import { getMaxRiskSubAggregations, getUpToMaxBuckets, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/chart_content.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/chart_content.tsx new file mode 100644 index 00000000000000..93b3f926429412 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/chart_content.tsx @@ -0,0 +1,61 @@ +/* + * 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 from 'react'; +import type { VisualizationEmbeddableProps } from '../../../../common/components/visualization_actions/types'; +import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; +import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; +import { AlertsCount } from './alerts_count'; +import type { AlertsCountAggregation } from './types'; + +type ChartContentProps = { + isChartEmbeddablesEnabled: boolean; +} & VisualizationEmbeddableProps & { + isLoadingAlerts: boolean; + alertsData: AlertSearchResponse | null; + stackByField0: string; + stackByField1: string | undefined; + }; + +const ChartContentComponent = ({ + alertsData, + extraActions, + extraOptions, + getLensAttributes, + height, + id, + inspectTitle, + isChartEmbeddablesEnabled, + isLoadingAlerts, + scopeId, + stackByField0, + stackByField1, + timerange, +}: ChartContentProps) => { + return isChartEmbeddablesEnabled ? ( + + ) : alertsData != null ? ( + + ) : null; +}; + +export const ChartContent = React.memo(ChartContentComponent); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index 4c28f99eac212c..c0b4d8bff6dfaf 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -22,7 +22,6 @@ import { InspectButtonContainer } from '../../../../common/components/inspect'; import { getAlertsCountQuery } from './helpers'; import * as i18n from './translations'; -import { AlertsCount } from './alerts_count'; import type { AlertsCountAggregation } from './types'; import { KpiPanel } from '../common/components'; import { useInspectButton } from '../common/hooks'; @@ -31,7 +30,7 @@ import { FieldSelection } from '../../../../common/components/field_selection'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { getAlertsTableLensAttributes as getLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_table'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; +import { ChartContent } from './chart_content'; export const DETECTIONS_ALERTS_COUNT_ID = 'detections-alerts-count'; @@ -215,27 +214,23 @@ export const AlertsCountPanel = memo( /> {toggleStatus ? ( - isChartEmbeddablesEnabled ? ( - - ) : alertsData != null ? ( - - ) : null + ) : null} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts index 5572933f6b424f..74eedc9772fafb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts @@ -8,6 +8,8 @@ import { useEffect, useState, useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import type { IFieldSubTypeNested } from '@kbn/es-query'; + import type { BrowserField } from '@kbn/timelines-plugin/common'; import type { GlobalTimeArgs } from '../../../../common/containers/use_global_time'; import { getScopeFromPath, useSourcererDataView } from '../../../../common/containers/sourcerer'; @@ -58,12 +60,21 @@ export const useInspectButton = ({ }, [setQuery, loading, response, request, refetch, uniqueQueryId, deleteQuery, searchSessionId]); }; +export function isDataViewFieldSubtypeNested(field: Partial) { + const subTypeNested = field?.subType as IFieldSubTypeNested; + return !!subTypeNested?.nested?.path; +} + +export function isKeyword(field: Partial) { + return field.esTypes && field.esTypes?.indexOf('keyword') >= 0; +} + export function getAggregatableFields(fields: { [fieldName: string]: Partial; }): EuiComboBoxOptionOption[] { const result = []; for (const [key, field] of Object.entries(fields)) { - if (field.aggregatable === true && field.esTypes && field.esTypes?.indexOf('keyword') >= 0) { + if (field.aggregatable === true && isKeyword(field) && !isDataViewFieldSubtypeNested(field)) { result.push({ label: key, value: key }); } } From df333d3c0156b6737fc5ffcc572837adf41b7156 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 2 Feb 2023 13:33:38 +0000 Subject: [PATCH 72/72] fix types --- .../components/alerts_kpis/alerts_count_panel/alerts_count.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.tsx index e944e496603fea..6c2e3fc008cfb0 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.tsx @@ -12,6 +12,8 @@ import styled from 'styled-components'; import { useUiSetting$ } from '../../../../common/lib/kibana'; import { DEFAULT_NUMBER_FORMAT } from '../../../../../common/constants'; +import type { AlertsCountAggregation } from './types'; +import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; import { getMaxRiskSubAggregations, getUpToMaxBuckets,