From 74e61b57d91ac9d8950048df4fc2d04f79137465 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Fri, 10 Feb 2023 20:37:40 +0000 Subject: [PATCH] [SecuritySolution] Fix alerts dropdown options (#150937) ## Summary Only provide Lens compatible options when the feature flag in on. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../components/field_selection/index.tsx | 4 ++ .../alerts_kpis/alerts_count_panel/index.tsx | 1 + .../alerts_histogram_panel/index.tsx | 10 +-- .../alerts_kpis/common/components.tsx | 10 +-- .../alerts_kpis/common/hooks.test.tsx | 67 ++++++++++++++++++- .../components/alerts_kpis/common/hooks.ts | 27 +++++--- 6 files changed, 99 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/field_selection/index.tsx b/x-pack/plugins/security_solution/public/common/components/field_selection/index.tsx index 6a62e539c4487..c7e6b0c065758 100644 --- a/x-pack/plugins/security_solution/public/common/components/field_selection/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/field_selection/index.tsx @@ -32,6 +32,7 @@ export interface Props { stackByField1ComboboxRef?: React.RefObject>; stackByWidth?: number; uniqueQueryId: string; + useLensCompatibleFields?: boolean; } const FieldSelectionComponent: React.FC = ({ @@ -46,6 +47,7 @@ const FieldSelectionComponent: React.FC = ({ stackByField1ComboboxRef, stackByWidth, uniqueQueryId, + useLensCompatibleFields, }: Props) => ( @@ -58,6 +60,7 @@ const FieldSelectionComponent: React.FC = ({ selected={stackByField0} inputRef={setStackByField0ComboboxInputRef} width={stackByWidth} + useLensCompatibleFields={useLensCompatibleFields} /> = ({ selected={stackByField1 ?? ''} inputRef={setStackByField1ComboboxInputRef} width={stackByWidth} + useLensCompatibleFields={useLensCompatibleFields} /> 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 8dbfb949cdaec..68d830c1b5775 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 @@ -235,6 +235,7 @@ export const AlertsCountPanel = memo( stackByField1ComboboxRef={stackByField1ComboboxRef} stackByWidth={stackByWidth} uniqueQueryId={uniqueQueryId} + useLensCompatibleFields={isChartEmbeddablesEnabled} /> {showCount && 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 de52db990d5c7..8c5760c20a70b 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 @@ -406,12 +406,13 @@ export const AlertsHistogramPanel = memo( {showStackBy && ( <> {showGroupByPlaceholder && ( @@ -422,11 +423,12 @@ export const AlertsHistogramPanel = memo( content={i18n.NOT_AVAILABLE_TOOLTIP} > 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 31b137e24cd15..085b66f249104 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 @@ -53,13 +53,14 @@ export const KpiPanel = styled(EuiPanel)<{ interface StackedBySelectProps { 'aria-label'?: string; 'data-test-subj'?: string; + dropDownoptions?: Array>; + inputRef?: (inputRef: HTMLInputElement | null) => void; isDisabled?: boolean; + onSelect: (selected: string) => void; prepend?: string; selected: string; - inputRef?: (inputRef: HTMLInputElement | null) => void; - onSelect: (selected: string) => void; + useLensCompatibleFields?: boolean; width?: number; - dropDownoptions?: Array>; } export const StackByComboBoxWrapper = styled.div<{ width: number }>` @@ -79,6 +80,7 @@ export const StackByComboBox = React.forwardRef( inputRef, width = DEFAULT_WIDTH, dropDownoptions, + useLensCompatibleFields, }: StackedBySelectProps, ref ) => { @@ -96,7 +98,7 @@ export const StackByComboBox = React.forwardRef( return [{ label: selected, value: selected }]; }, [selected]); - const stackOptions = useStackByFields(); + const stackOptions = useStackByFields(useLensCompatibleFields); const singleSelection = useMemo(() => { return { asPlainText: true }; }, []); 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 24ba801ed0338..f9f82cf41682a 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 @@ -11,18 +11,40 @@ import type { UseInspectButtonParams } from './hooks'; import { getAggregatableFields, useInspectButton, useStackByFields } from './hooks'; import { mockBrowserFields } from '../../../../common/containers/source/mock'; import { TestProviders } from '../../../../common/mock'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; }); +jest.mock('../../../../common/containers/sourcerer', () => ({ + useSourcererDataView: jest.fn(), + getScopeFromPath: jest.fn(), +})); test('getAggregatableFields', () => { expect(getAggregatableFields(mockBrowserFields)).toMatchSnapshot(); }); +test('getAggregatableFields when useLensCompatibleFields = true', () => { + const useLensCompatibleFields = true; + expect( + getAggregatableFields({ base: mockBrowserFields.base }, useLensCompatibleFields) + ).toHaveLength(0); +}); + describe('hooks', () => { + const mockUseSourcererDataView = useSourcererDataView as jest.Mock; + describe('useInspectButton', () => { + beforeEach(() => { + mockUseSourcererDataView.mockReturnValue({ + browserFields: mockBrowserFields, + }); + + jest.clearAllMocks(); + }); + const defaultParams: UseInspectButtonParams = { setQuery: jest.fn(), response: '', @@ -58,9 +80,13 @@ describe('hooks', () => { }); describe('useStackByFields', () => { - jest.mock('../../../../common/containers/sourcerer', () => ({ - useSourcererDataView: jest.fn().mockReturnValue({ browserFields: mockBrowserFields }), - })); + beforeEach(() => { + mockUseSourcererDataView.mockReturnValue({ + browserFields: mockBrowserFields, + }); + + jest.clearAllMocks(); + }); it('returns only aggregateable fields', () => { const wrapper = ({ children }: { children: JSX.Element }) => ( {children} @@ -73,5 +99,40 @@ describe('hooks', () => { aggregateableFields?.find((field) => field.label === 'nestedField.firstAttributes') ).toBe(undefined); }); + + it('returns only Lens compatible fields (check if one of esTypes is keyword)', () => { + mockUseSourcererDataView.mockReturnValue({ + browserFields: { base: mockBrowserFields.base }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const useLensCompatibleFields = true; + const { result, unmount } = renderHook(() => useStackByFields(useLensCompatibleFields), { + wrapper, + }); + const aggregateableFields = result.current; + unmount(); + expect(aggregateableFields?.find((field) => field.label === '@timestamp')).toBeUndefined(); + expect(aggregateableFields?.find((field) => field.label === '_id')).toBeUndefined(); + }); + + it('returns only Lens compatible fields (check if it is a nested field)', () => { + mockUseSourcererDataView.mockReturnValue({ + browserFields: { nestedField: mockBrowserFields.nestedField }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const useLensCompatibleFields = true; + const { result, unmount } = renderHook(() => useStackByFields(useLensCompatibleFields), { + wrapper, + }); + const aggregateableFields = result.current; + unmount(); + expect(aggregateableFields).toHaveLength(0); + }); }); }); 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 74eedc9772faf..66fc17b157df7 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 @@ -69,28 +69,37 @@ export function isKeyword(field: Partial) { return field.esTypes && field.esTypes?.indexOf('keyword') >= 0; } -export function getAggregatableFields(fields: { - [fieldName: string]: Partial; -}): EuiComboBoxOptionOption[] { +export function getAggregatableFields( + fields: { + [fieldName: string]: Partial; + }, + useLensCompatibleFields?: boolean +): EuiComboBoxOptionOption[] { const result = []; for (const [key, field] of Object.entries(fields)) { - if (field.aggregatable === true && isKeyword(field) && !isDataViewFieldSubtypeNested(field)) { - result.push({ label: key, value: key }); + if (useLensCompatibleFields) { + if (field.aggregatable === true && isKeyword(field) && !isDataViewFieldSubtypeNested(field)) { + result.push({ label: key, value: key }); + } + } else { + if (field.aggregatable === true) { + result.push({ label: key, value: key }); + } } } return result; } -export const useStackByFields = () => { +export const useStackByFields = (useLensCompatibleFields?: boolean) => { const { pathname } = useLocation(); const { browserFields } = useSourcererDataView(getScopeFromPath(pathname)); const allFields = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); const [stackByFieldOptions, setStackByFieldOptions] = useState(() => - getAggregatableFields(allFields) + getAggregatableFields(allFields, useLensCompatibleFields) ); useEffect(() => { - setStackByFieldOptions(getAggregatableFields(allFields)); - }, [allFields]); + setStackByFieldOptions(getAggregatableFields(allFields, useLensCompatibleFields)); + }, [allFields, useLensCompatibleFields]); return useMemo(() => stackByFieldOptions, [stackByFieldOptions]); };