diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts index 4df376acb256e..460264e776838 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts @@ -19,7 +19,7 @@ export interface EventSource { } export interface EventsActionGroupData { - key: number; + key: number | string; events: { bucket: EventsMatrixHistogramData[]; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.test.tsx index b459f13a85480..4aa68e524f846 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.test.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { showInitialLoadingSpinner } from './helpers'; +import { formatAlertsData, showInitialLoadingSpinner } from './helpers'; +import { result, textResult, stackedByBooleanField, stackedByTextField } from './mock_data'; describe('helpers', () => { describe('showInitialLoadingSpinner', () => { @@ -34,3 +35,15 @@ describe('helpers', () => { }); }); }); + +describe('formatAlertsData', () => { + test('stack by a boolean field', () => { + const res = formatAlertsData(stackedByBooleanField); + expect(res).toEqual(result); + }); + + test('stack by a text field', () => { + const res = formatAlertsData(stackedByTextField); + expect(res).toEqual(textResult); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.tsx index 1f5c67c61b9e2..a20a3e1c37e83 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.tsx @@ -17,19 +17,22 @@ const EMPTY_ALERTS_DATA: HistogramData[] = []; export const formatAlertsData = (alertsData: AlertSearchResponse<{}, AlertsAggregation> | null) => { const groupBuckets: AlertsGroupBucket[] = alertsData?.aggregations?.alertsByGrouping?.buckets ?? []; - return groupBuckets.reduce((acc, { key: group, alerts }) => { - const alertsBucket: AlertsBucket[] = alerts.buckets ?? []; + return groupBuckets.reduce( + (acc, { key_as_string: keyAsString, key: group, alerts }) => { + const alertsBucket: AlertsBucket[] = alerts.buckets ?? []; - return [ - ...acc, - // eslint-disable-next-line @typescript-eslint/naming-convention - ...alertsBucket.map(({ key, doc_count }: AlertsBucket) => ({ - x: key, - y: doc_count, - g: group, - })), - ]; - }, EMPTY_ALERTS_DATA); + return [ + ...acc, + // eslint-disable-next-line @typescript-eslint/naming-convention + ...alertsBucket.map(({ key, doc_count }: AlertsBucket) => ({ + x: key, + y: doc_count, + g: keyAsString ?? group.toString(), + })), + ]; + }, + EMPTY_ALERTS_DATA + ); }; export const getAlertsHistogramQuery = ( 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 84476c3ee6885..85354a1148dcc 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 @@ -186,7 +186,7 @@ export const AlertsHistogramPanel = memo( ), field: selectedStackByOption, timelineId, - value: bucket.key, + value: bucket?.key_as_string ?? bucket.key, })) : NO_LEGEND_DATA, [ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/mock_data.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/mock_data.ts new file mode 100644 index 0000000000000..6e5551eb69201 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/mock_data.ts @@ -0,0 +1,83 @@ +/* + * 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 stackedByBooleanField = { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { + total: { + value: 3, + relation: 'eq', + }, + hits: [], + }, + timeout: false, + aggregations: { + alertsByGrouping: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 1, + key_as_string: 'true', + doc_count: 2683, + alerts: { + buckets: [ + { key_as_string: '2022-05-10T15:34:48.075Z', key: 1652196888075, doc_count: 0 }, + { key_as_string: '2022-05-10T16:19:48.074Z', key: 1652199588074, doc_count: 0 }, + { key_as_string: '2022-05-10T17:04:48.073Z', key: 1652202288073, doc_count: 0 }, + ], + }, + }, + ], + }, + }, +}; + +export const result = [ + { x: 1652196888075, y: 0, g: 'true' }, + { x: 1652199588074, y: 0, g: 'true' }, + { x: 1652202288073, y: 0, g: 'true' }, +]; + +export const stackedByTextField = { + took: 1, + timeout: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { + total: { + value: 3, + relation: 'eq', + }, + hits: [], + }, + aggregations: { + alertsByGrouping: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'MacBook-Pro.local', + doc_count: 2706, + alerts: { + buckets: [ + { key_as_string: '2022-05-10T15:34:48.075Z', key: 1652196888075, doc_count: 0 }, + { key_as_string: '2022-05-10T16:19:48.074Z', key: 1652199588074, doc_count: 0 }, + { key_as_string: '2022-05-10T17:04:48.073Z', key: 1652202288073, doc_count: 0 }, + ], + }, + }, + ], + }, + }, +}; + +export const textResult = [ + { x: 1652196888075, y: 0, g: 'MacBook-Pro.local' }, + { x: 1652199588074, y: 0, g: 'MacBook-Pro.local' }, + { x: 1652202288073, y: 0, g: 'MacBook-Pro.local' }, +]; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/types.ts index 8c2a53dc23d43..433fee1716a47 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/types.ts @@ -26,7 +26,8 @@ export interface AlertsBucket { } export interface AlertsGroupBucket { - key: string; + key: string | number; + key_as_string?: string; alerts: { buckets: AlertsBucket[]; }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/helpers.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/helpers.test.ts new file mode 100644 index 0000000000000..2680b604c6e28 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/helpers.test.ts @@ -0,0 +1,22 @@ +/* + * 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 { MatrixHistogramType } from '../../../../../common/search_strategy'; +import { getGenericData } from './helpers'; +import { stackedByBooleanField, stackedByTextField, result, textResult } from './mock_data'; + +describe('getGenericData', () => { + test('stack by a boolean field', () => { + const res = getGenericData(stackedByBooleanField, 'events.bucket'); + expect(res).toEqual(result); + }); + + test('stack by a text field', () => { + const res = getGenericData(stackedByTextField, 'events.bucket'); + expect(res).toEqual(textResult); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/helpers.ts index c8ede95d166c7..7a8e2014f5c76 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/helpers.ts @@ -18,7 +18,8 @@ export const getGenericData = ( ): MatrixHistogramData[] => { let result: MatrixHistogramData[] = []; data.forEach((bucketData: unknown) => { - const group = get('key', bucketData); + // if key_as_string is present use it, else default to the existing key + const group = get('key_as_string', bucketData) ?? get('key', bucketData); const histData = getOr([], keyBucket, bucketData).map( // eslint-disable-next-line @typescript-eslint/naming-convention ({ key, doc_count }: MatrixHistogramBucket) => ({ diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/mock_data.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/mock_data.ts new file mode 100644 index 0000000000000..9a938846826a1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/matrix_histogram/mock_data.ts @@ -0,0 +1,46 @@ +/* + * 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 stackedByBooleanField = [ + { + key: 1, + key_as_string: 'true', + doc_count: 7125, + events: { + bucket: [ + { key_as_string: '2022-05-10T15:34:48.075Z', key: 1652196888075, doc_count: 0 }, + { key_as_string: '2022-05-10T16:19:48.074Z', key: 1652199588074, doc_count: 774 }, + { key_as_string: '2022-05-10T17:04:48.073Z', key: 1652202288073, doc_count: 415 }, + ], + }, + }, +]; +export const result = [ + { x: 1652196888075, y: 0, g: 'true' }, + { x: 1652199588074, y: 774, g: 'true' }, + { x: 1652202288073, y: 415, g: 'true' }, +]; + +export const stackedByTextField = [ + { + key: 'MacBook-Pro.local', + doc_count: 7103, + events: { + bucket: [ + { key_as_string: '2022-05-10T15:34:48.075Z', key: 1652196888075, doc_count: 0 }, + { key_as_string: '2022-05-10T16:19:48.074Z', key: 1652199588074, doc_count: 774 }, + { key_as_string: '2022-05-10T17:04:48.073Z', key: 1652202288073, doc_count: 415 }, + ], + }, + }, +]; + +export const textResult = [ + { x: 1652196888075, y: 0, g: 'MacBook-Pro.local' }, + { x: 1652199588074, y: 774, g: 'MacBook-Pro.local' }, + { x: 1652202288073, y: 415, g: 'MacBook-Pro.local' }, +];