From 5f746d65611efe02b24a40c9ac96598d8a61189b Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 7 Dec 2021 08:03:17 -0500 Subject: [PATCH 01/10] Use EuiComboBox in place of a select for alert page visuals --- .../alerts_count_panel/helpers.tsx | 3 +- .../alerts_kpis/alerts_count_panel/index.tsx | 9 ++-- .../alerts_histogram_panel/helpers.tsx | 3 +- .../alerts_histogram_panel/index.tsx | 16 +++--- .../alerts_kpis/common/components.tsx | 51 ++++++++++++------- .../alerts_kpis/common/hooks.test.tsx | 27 +++++++++- .../components/alerts_kpis/common/hooks.ts | 31 ++++++++++- .../alerts_kpis/common/translations.ts | 14 +++++ .../components/create_field_button/index.tsx | 2 +- 9 files changed, 120 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/helpers.tsx index bd747fb637cb8..cd407a125cdb6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/helpers.tsx @@ -6,10 +6,9 @@ */ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../common/constants'; -import type { AlertsStackByField } from '../common/types'; export const getAlertsCountQuery = ( - stackByField: AlertsStackByField, + stackByField: string, from: string, to: string, additionalFilters: Array<{ 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 94b09c4a5ea21..21c02416ef60f 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 @@ -21,8 +21,7 @@ import * as i18n from './translations'; import { AlertsCount } from './alerts_count'; import type { AlertsCountAggregation } from './types'; import { DEFAULT_STACK_BY_FIELD } from '../common/config'; -import type { AlertsStackByField } from '../common/types'; -import { KpiPanel, StackBySelect } from '../common/components'; +import { KpiPanel, StackByComboBox } from '../common/components'; import { useInspectButton } from '../common/hooks'; export const DETECTIONS_ALERTS_COUNT_ID = 'detections-alerts-count'; @@ -40,7 +39,7 @@ export const AlertsCountPanel = memo( // create a unique, but stable (across re-renders) query id const uniqueQueryId = useMemo(() => `${DETECTIONS_ALERTS_COUNT_ID}-${uuid.v4()}`, []); const [selectedStackByOption, setSelectedStackByOption] = - useState(DEFAULT_STACK_BY_FIELD); + useState(DEFAULT_STACK_BY_FIELD); // TODO: Once we are past experimental phase this code should be removed // const fetchMethod = useIsExperimentalFeatureEnabled('ruleRegistryEnabled') @@ -92,14 +91,14 @@ export const AlertsCountPanel = memo( return ( - + - + ( const [isInspectDisabled, setIsInspectDisabled] = useState(false); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); const [totalAlertsObj, setTotalAlertsObj] = useState(defaultTotalAlertsObj); - const [selectedStackByOption, setSelectedStackByOption] = useState( + const [selectedStackByOption, setSelectedStackByOption] = useState( onlyField == null ? defaultStackByOption : onlyField ); @@ -261,7 +261,7 @@ export const AlertsHistogramPanel = memo( () => (onlyField == null ? title : i18n.TOP(onlyField)), [onlyField, title] ); - + // TODO: histogram here return ( @@ -276,10 +276,12 @@ export const AlertsHistogramPanel = memo( {showStackBy && ( - + <> + + )} {headerChildren != null && headerChildren} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/components.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/components.tsx index 53d41835d6bb9..c624dd3025cbf 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/components.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/components.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import { EuiPanel, EuiSelect } from '@elastic/eui'; +import { EuiPanel, EuiComboBox } from '@elastic/eui'; import styled from 'styled-components'; -import React, { useCallback } from 'react'; -import { PANEL_HEIGHT, MOBILE_PANEL_HEIGHT, alertsStackByOptions } from './config'; -import type { AlertsStackByField } from './types'; +import React, { useCallback, useMemo } from 'react'; +import { PANEL_HEIGHT, MOBILE_PANEL_HEIGHT } from './config'; +import { useStackByFields } from './hooks'; import * as i18n from './translations'; export const KpiPanel = styled(EuiPanel)<{ height?: number }>` @@ -25,24 +25,41 @@ export const KpiPanel = styled(EuiPanel)<{ height?: number }>` } `; interface StackedBySelectProps { - selected: AlertsStackByField; - onSelect: (selected: AlertsStackByField) => void; + selected: string; + onSelect: (selected: string) => void; } -export const StackBySelect: React.FC = ({ selected, onSelect }) => { - const setSelectedOptionCallback = useCallback( - (event: React.ChangeEvent) => { - onSelect(event.target.value as AlertsStackByField); +export const StackByComboBoxWrapper = styled.div` + min-width: 350px; +`; + +export const StackByComboBox: React.FC = ({ selected, onSelect }) => { + const onChange = useCallback( + (options) => { + if (options && options.length > 0) { + onSelect(options[0].value); + } else { + onSelect(''); + } }, [onSelect] ); - + const selectedOptions = useMemo(() => { + return [{ label: selected, value: selected }]; + }, [selected]); + const stackOptions = useStackByFields(); return ( - + + + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx index ad0fc1fa7ac61..5c8b2f5988317 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx @@ -5,8 +5,19 @@ * 2.0. */ +import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; -import { useInspectButton, UseInspectButtonParams } from './hooks'; +import { useInspectButton, UseInspectButtonParams, useStackByFields } from './hooks'; +import { mockBrowserFields } from '../../../../common/containers/source/mock'; +import { + TestProviders, +} from '../../../../common/mock'; + + +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; +}); describe('hooks', () => { describe('useInspectButton', () => { @@ -43,4 +54,18 @@ describe('hooks', () => { expect(mockDeleteQuery).toHaveBeenCalledWith({ id: defaultParams.uniqueQueryId }); }); }); + + describe('useStackByFields', () => { + jest.mock('../../../../common/containers/sourcerer', () => ({ + useSourcererDataView: jest.fn().mockReturnValue({ browserFields: mockBrowserFields }), + })); + it('returns only aggregateable fields', () => { + const wrapper = ({ children }: { children: JSX.Element }) => {children} + const { result, unmount } = renderHook(() => useStackByFields(), { wrapper }); + const aggregateableFields = result.current; + unmount(); + expect(aggregateableFields?.find(field => field.label === 'agent.id')).toBeTruthy(); + expect(aggregateableFields?.find(field => field.label === 'nestedField.firstAttributes')).toBe(undefined); + }); + }); }); 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 6375e2b0c27fb..c67c96166e2c3 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 @@ -5,8 +5,12 @@ * 2.0. */ -import { useEffect } from 'react'; +import { useEffect, useState, useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; +import type { EuiComboBoxOptionOption } from '@elastic/eui'; import type { GlobalTimeArgs } from '../../../../common/containers/use_global_time'; +import { getScopeFromPath, useSourcererDataView } from '../../../../common/containers/sourcerer'; +import { getAllFieldsByName } from '../../../../common/containers/source'; export interface UseInspectButtonParams extends Pick { response: string; @@ -15,6 +19,7 @@ export interface UseInspectButtonParams extends Pick { + const [stackByFieldOptions, setStackByFieldOptions] = useState< + undefined | EuiComboBoxOptionOption[] + >(undefined); + const { pathname } = useLocation(); + + const { browserFields } = useSourcererDataView(getScopeFromPath(pathname)); + const allFields = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); + useEffect(() => { + const options = Object.entries(allFields).reduce( + (prev: EuiComboBoxOptionOption[], [key, field]) => { + if (field.aggregatable === true) { + return [...prev, { label: key, value: key }]; + } else { + return prev; + } + }, + [] + ); + setStackByFieldOptions(options); + }, [allFields]); + return useMemo(() => stackByFieldOptions, [stackByFieldOptions]); +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/translations.ts index d99e1d4744ae7..d45563675154e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/translations.ts @@ -13,3 +13,17 @@ export const STACK_BY_LABEL = i18n.translate( defaultMessage: 'Stack by', } ); + +export const STACK_BY_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.stackByPlaceholder', + { + defaultMessage: 'Select a field to stack by', + } +); + +export const STACK_BY_ARIA_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.histogram.stackByOptions.stackByAriaLabel', + { + defaultMessage: 'Stack the alerts histogram by a field value', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx index 088c37d36c167..ce808f2d8c844 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx @@ -42,7 +42,7 @@ export const CreateFieldButton = React.memo( dataViewFieldEditor, data: { dataViews }, } = useKibana().services; - + console.log(selectedDataViewId); useEffect(() => { dataViews.get(selectedDataViewId).then((dataViewResponse) => { setDataView(dataViewResponse); From 2664701eecb7293b0120d975b983c89bd1be088d Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 7 Dec 2021 08:16:52 -0500 Subject: [PATCH 02/10] Remove console.log --- .../public/timelines/components/create_field_button/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx index 76af049ad7d29..75e5b59df1ffa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx @@ -42,7 +42,7 @@ export const CreateFieldButton = React.memo( dataViewFieldEditor, data: { dataViews }, } = useKibana().services; - console.log(selectedDataViewId); + useEffect(() => { dataViews.get(selectedDataViewId).then((dataViewResponse) => { setDataView(dataViewResponse); From 204fd691f6040cddccfdae424966195a1c3b1721 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 7 Dec 2021 08:18:31 -0500 Subject: [PATCH 03/10] Better variable name in reduce function --- .../detections/components/alerts_kpis/common/hooks.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 c67c96166e2c3..cf259f3a07ce5 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 @@ -64,11 +64,11 @@ export const useStackByFields = () => { const allFields = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); useEffect(() => { const options = Object.entries(allFields).reduce( - (prev: EuiComboBoxOptionOption[], [key, field]) => { + (filteredOptions: EuiComboBoxOptionOption[], [key, field]) => { if (field.aggregatable === true) { - return [...prev, { label: key, value: key }]; + return [...filteredOptions, { label: key, value: key }]; } else { - return prev; + return filteredOptions; } }, [] From 7b0b609d704f58918be45990847a1f7e4d7f9a60 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 7 Dec 2021 08:19:36 -0500 Subject: [PATCH 04/10] Remove comment --- .../components/alerts_kpis/alerts_histogram_panel/index.tsx | 2 +- 1 file changed, 1 insertion(+), 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 fa67174e6ef5e..11dbb4da863db 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 @@ -261,7 +261,7 @@ export const AlertsHistogramPanel = memo( () => (onlyField == null ? title : i18n.TOP(onlyField)), [onlyField, title] ); - // TODO: histogram here + return ( From c2bc60cb381aae2f782037180a11934ecf0e9414 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 7 Dec 2021 08:42:19 -0500 Subject: [PATCH 05/10] Remove typo --- .../components/alerts_kpis/alerts_count_panel/index.tsx | 2 +- 1 file changed, 1 insertion(+), 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 21c02416ef60f..bed1418282417 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 @@ -91,7 +91,7 @@ export const AlertsCountPanel = memo( return ( - + Date: Tue, 7 Dec 2021 10:30:36 -0500 Subject: [PATCH 06/10] Fix linting/tests --- .../alerts_histogram_panel/index.test.tsx | 1 + .../components/alerts_kpis/common/hooks.test.tsx | 15 ++++++++------- .../detection_engine/detection_engine.test.tsx | 5 +++++ 3 files changed, 14 insertions(+), 7 deletions(-) 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 09c184bb62bd5..a6437a0efacd9 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 @@ -22,6 +22,7 @@ jest.mock('react-router-dom', () => { ...originalModule, createHref: jest.fn(), useHistory: jest.fn(), + useLocation: jest.fn().mockReturnValue({ pathname: '' }), }; }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx index 5c8b2f5988317..d68c5c303cfd7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx @@ -9,10 +9,7 @@ import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; import { useInspectButton, UseInspectButtonParams, useStackByFields } from './hooks'; import { mockBrowserFields } from '../../../../common/containers/source/mock'; -import { - TestProviders, -} from '../../../../common/mock'; - +import { TestProviders } from '../../../../common/mock'; jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); @@ -60,12 +57,16 @@ describe('hooks', () => { useSourcererDataView: jest.fn().mockReturnValue({ browserFields: mockBrowserFields }), })); it('returns only aggregateable fields', () => { - const wrapper = ({ children }: { children: JSX.Element }) => {children} + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); const { result, unmount } = renderHook(() => useStackByFields(), { wrapper }); const aggregateableFields = result.current; unmount(); - expect(aggregateableFields?.find(field => field.label === 'agent.id')).toBeTruthy(); - expect(aggregateableFields?.find(field => field.label === 'nestedField.firstAttributes')).toBe(undefined); + expect(aggregateableFields?.find((field) => field.label === 'agent.id')).toBeTruthy(); + expect( + aggregateableFields?.find((field) => field.label === 'nestedField.firstAttributes') + ).toBe(undefined); }); }); }); 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 eeb22a2aa071c..c5d053c57fc97 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 @@ -23,6 +23,7 @@ import { useSourcererDataView } from '../../../common/containers/sourcerer'; import { createStore, State } from '../../../common/store'; import { mockHistory, Router } from '../../../common/mock/router'; import { mockTimelines } from '../../../common/mock/mock_timelines_plugin'; +import { mockBrowserFields } from '../../../common/containers/source/mock'; // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -71,6 +72,9 @@ jest.mock('../../../common/lib/kibana', () => { siem: { crud_alerts: true, read_alerts: true }, }, }, + uiSettings: { + get: jest.fn(), + }, timelines: { ...mockTimelines }, data: { query: { @@ -113,6 +117,7 @@ describe('DetectionEnginePageComponent', () => { (useSourcererDataView as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, + browserFields: mockBrowserFields, }); }); From e3dd0f3232192961f8bf6c86f82fe6c64ad5e20f Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 13 Dec 2021 14:03:06 -0500 Subject: [PATCH 07/10] PR feedback --- .../alerts_kpis/alerts_count_panel/index.tsx | 3 +- .../alerts_kpis/common/components.tsx | 8 +++-- .../components/alerts_kpis/common/hooks.ts | 30 ++++++++++--------- 3 files changed, 23 insertions(+), 18 deletions(-) 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 bed1418282417..b69f4f1f498f9 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 @@ -38,8 +38,7 @@ export const AlertsCountPanel = memo( // create a unique, but stable (across re-renders) query id const uniqueQueryId = useMemo(() => `${DETECTIONS_ALERTS_COUNT_ID}-${uuid.v4()}`, []); - const [selectedStackByOption, setSelectedStackByOption] = - useState(DEFAULT_STACK_BY_FIELD); + const [selectedStackByOption, setSelectedStackByOption] = useState(DEFAULT_STACK_BY_FIELD); // TODO: Once we are past experimental phase this code should be removed // const fetchMethod = useIsExperimentalFeatureEnabled('ruleRegistryEnabled') diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/components.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/components.tsx index c624dd3025cbf..6a56f7bc220ac 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/components.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/components.tsx @@ -30,7 +30,7 @@ interface StackedBySelectProps { } export const StackByComboBoxWrapper = styled.div` - min-width: 350px; + width: 400px; `; export const StackByComboBox: React.FC = ({ selected, onSelect }) => { @@ -48,16 +48,20 @@ export const StackByComboBox: React.FC = ({ selected, onSe return [{ label: selected, value: selected }]; }, [selected]); const stackOptions = useStackByFields(); + const singleSelection = useMemo(() => { + return { asPlainText: true }; + }, []); return ( 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 cf259f3a07ce5..4f2ec4a67cc90 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,7 @@ import { useEffect, useState, useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import type { BrowserField } from '../../../../../../timelines/common'; import type { GlobalTimeArgs } from '../../../../common/containers/use_global_time'; import { getScopeFromPath, useSourcererDataView } from '../../../../common/containers/sourcerer'; import { getAllFieldsByName } from '../../../../common/containers/source'; @@ -54,26 +55,27 @@ export const useInspectButton = ({ }, [setQuery, loading, response, request, refetch, uniqueQueryId, deleteQuery]); }; +function fieldsToOptions(fields: { [fieldName: string]: Partial }) { + return Object.entries(fields).reduce( + (filteredOptions: EuiComboBoxOptionOption[], [key, field]) => { + if (field.aggregatable === true) { + return [...filteredOptions, { label: key, value: key }]; + } else { + return filteredOptions; + } + }, + [] + ); +} + export const useStackByFields = () => { - const [stackByFieldOptions, setStackByFieldOptions] = useState< - undefined | EuiComboBoxOptionOption[] - >(undefined); const { pathname } = useLocation(); const { browserFields } = useSourcererDataView(getScopeFromPath(pathname)); const allFields = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); + const [stackByFieldOptions, setStackByFieldOptions] = useState(() => fieldsToOptions(allFields)); useEffect(() => { - const options = Object.entries(allFields).reduce( - (filteredOptions: EuiComboBoxOptionOption[], [key, field]) => { - if (field.aggregatable === true) { - return [...filteredOptions, { label: key, value: key }]; - } else { - return filteredOptions; - } - }, - [] - ); - setStackByFieldOptions(options); + setStackByFieldOptions(fieldsToOptions(allFields)); }, [allFields]); return useMemo(() => stackByFieldOptions, [stackByFieldOptions]); }; From b9e89b6a082e7691637a723112dc989de1647872 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 13 Dec 2021 15:21:25 -0500 Subject: [PATCH 08/10] Add missing mocks --- .../alerts_histogram_panel/index.test.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 a6437a0efacd9..eb06f85a0a76f 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 @@ -38,9 +38,21 @@ jest.mock('../../../../common/lib/kibana/kibana_react', () => { navigateToApp: mockNavigateToApp, getUrlForApp: jest.fn(), }, + data: { + search: { + search: jest.fn(), + }, + }, uiSettings: { get: jest.fn(), }, + notifications: { + toasts: { + addWarning: jest.fn(), + addError: jest.fn(), + addSuccess: jest.fn(), + }, + }, }, }), }; From 7167204c19a1fa89ad2be608c801a532a518969f Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 15 Dec 2021 12:36:42 -0500 Subject: [PATCH 09/10] Add router mocks --- .../alerts_kpis/alerts_count_panel/alerts_count.test.tsx | 5 +++++ .../components/alerts_kpis/alerts_count_panel/index.test.tsx | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.test.tsx index 561126f3264ad..4fce0361d5d13 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.test.tsx @@ -18,6 +18,11 @@ import { AlertsCountAggregation } from './types'; jest.mock('../../../../common/lib/kibana'); const mockDispatch = jest.fn(); +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; +}); + jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); return { 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 9660916d4f32c..d0b05587a4711 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 @@ -13,6 +13,11 @@ import { TestProviders } from '../../../../common/mock'; import { AlertsCountPanel } from './index'; +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; +}); + describe('AlertsCountPanel', () => { const defaultProps = { signalIndexName: 'signalIndexName', From f00491424fafd55c8d27b437a98af871b26b3e03 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 15 Dec 2021 15:10:51 -0500 Subject: [PATCH 10/10] Rename getAggregatableFields --- .../detections/components/alerts_kpis/common/hooks.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 4f2ec4a67cc90..65b87670810b0 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 @@ -55,7 +55,7 @@ export const useInspectButton = ({ }, [setQuery, loading, response, request, refetch, uniqueQueryId, deleteQuery]); }; -function fieldsToOptions(fields: { [fieldName: string]: Partial }) { +function getAggregatableFields(fields: { [fieldName: string]: Partial }) { return Object.entries(fields).reduce( (filteredOptions: EuiComboBoxOptionOption[], [key, field]) => { if (field.aggregatable === true) { @@ -73,9 +73,11 @@ export const useStackByFields = () => { const { browserFields } = useSourcererDataView(getScopeFromPath(pathname)); const allFields = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); - const [stackByFieldOptions, setStackByFieldOptions] = useState(() => fieldsToOptions(allFields)); + const [stackByFieldOptions, setStackByFieldOptions] = useState(() => + getAggregatableFields(allFields) + ); useEffect(() => { - setStackByFieldOptions(fieldsToOptions(allFields)); + setStackByFieldOptions(getAggregatableFields(allFields)); }, [allFields]); return useMemo(() => stackByFieldOptions, [stackByFieldOptions]); };