[8.8] [Security Solution] [Fix] [Performance] Alert Page Controls should query only from Alert Index. (#157286) (#160138)

# Backport

This will backport the following commits from `main` to `8.8`:
- [[Security Solution] [Fix] [Performance] Alert Page Controls should
query only from Alert Index.
(#157286)](https://github.com/elastic/kibana/pull/157286)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Jatin
Kathuria","email":"jatin.kathuria@elastic.co"},"sourceCommit":{"committedDate":"2023-06-21T12:21:16Z","message":"[Security
Solution] [Fix] [Performance] Alert Page Controls should query only from
Alert Index. (#157286)\n\n## Summary\r\n\r\nHandles :
#157217\r\n\r\n\r\n|Before|After|\r\n|---|---|\r\n|
<video\r\nsrc=\"aa892faf-8055-46e0-b23b-87061ecef67f\"\r\n/>
|
<video\r\nsrc=\"2f392ea4-cfc5-4759-96c7-94561f60c3f4\"\r\n/>
|","sha":"ac5207cca96db362ed31387e16e5b6849167f565","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:Threat
Hunting:Investigations","backport:prev-MAJOR","v8.9.0"],"number":157286,"url":"https://github.com/elastic/kibana/pull/157286","mergeCommit":{"message":"[Security
Solution] [Fix] [Performance] Alert Page Controls should query only from
Alert Index. (#157286)\n\n## Summary\r\n\r\nHandles :
#157217\r\n\r\n\r\n|Before|After|\r\n|---|---|\r\n|
<video\r\nsrc=\"aa892faf-8055-46e0-b23b-87061ecef67f\"\r\n/>
|
<video\r\nsrc=\"2f392ea4-cfc5-4759-96c7-94561f60c3f4\"\r\n/>
|","sha":"ac5207cca96db362ed31387e16e5b6849167f565"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/157286","number":157286,"mergeCommit":{"message":"[Security
Solution] [Fix] [Performance] Alert Page Controls should query only from
Alert Index. (#157286)\n\n## Summary\r\n\r\nHandles :
#157217\r\n\r\n\r\n|Before|After|\r\n|---|---|\r\n|
<video\r\nsrc=\"aa892faf-8055-46e0-b23b-87061ecef67f\"\r\n/>
|
<video\r\nsrc=\"2f392ea4-cfc5-4759-96c7-94561f60c3f4\"\r\n/>
|","sha":"ac5207cca96db362ed31387e16e5b6849167f565"}}]}] BACKPORT-->
This commit is contained in:
Jatin Kathuria 2023-06-22 08:54:53 -07:00 committed by GitHub
parent 3888dc0309
commit 5df083d62f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 132 additions and 44 deletions

View file

@ -10,13 +10,16 @@ import { getNewRule } from '../../objects/rule';
import {
CONTROL_FRAMES,
CONTROL_FRAME_TITLE,
CONTROL_POPOVER,
FILTER_GROUP_CHANGED_BANNER,
FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS,
FILTER_GROUP_SAVE_CHANGES_POPOVER,
OPTION_IGNORED,
OPTION_LIST_LABELS,
OPTION_LIST_VALUES,
OPTION_SELECTABLE,
OPTION_SELECTABLE_COUNT,
FILTER_GROUP_CONTROL_ACTION_EDIT,
FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS,
} from '../../screens/common/filter_group';
import { createRule } from '../../tasks/api_calls/rules';
import { cleanKibana } from '../../tasks/common';
@ -27,15 +30,17 @@ import { formatPageFilterSearchParam } from '../../../common/utils/format_page_f
import {
closePageFilterPopover,
markAcknowledgedFirstAlert,
openFirstAlert,
openPageFilterPopover,
resetFilters,
selectCountTable,
togglePageFilterPopover,
visitAlertsPageWithCustomFilters,
waitForAlerts,
waitForPageFilters,
} from '../../tasks/alerts';
import { ALERTS_COUNT } from '../../screens/alerts';
import { navigateFromHeaderTo } from '../../tasks/security_header';
import { ALERTS_COUNT, ALERTS_REFRESH_BTN } from '../../screens/alerts';
import { kqlSearch, navigateFromHeaderTo } from '../../tasks/security_header';
import { ALERTS, CASES } from '../../screens/security_header';
import {
addNewFilterGroupControlValues,
@ -46,6 +51,9 @@ import {
editFilterGroupControls,
saveFilterGroupControls,
} from '../../tasks/common/filter_group';
import { TOASTER } from '../../screens/alerts_detection_rules';
import { setEndDate, setStartDate } from '../../tasks/date_picker';
import { fillAddFilterForm, openAddFilterPopover } from '../../tasks/search_bar';
const customFilters = [
{
@ -231,13 +239,21 @@ describe('Detections : Page Filters', { testIsolation: false }, () => {
markAcknowledgedFirstAlert();
waitForAlerts();
cy.get(OPTION_LIST_VALUES(0)).click();
cy.get(OPTION_SELECTABLE(0, 'acknowledged')).should('be.visible');
cy.get(OPTION_SELECTABLE(0, 'acknowledged')).should('be.visible').trigger('click');
cy.get(ALERTS_COUNT)
.invoke('text')
.should((newAlertCount) => {
expect(newAlertCount.split(' ')[0]).eq(String(parseInt(originalAlertCount, 10) - 1));
});
});
// cleanup
// revert the changes so that data does not change for further tests.
// It would make sure that tests can run in any order.
cy.get(OPTION_SELECTABLE(0, 'open')).trigger('click');
togglePageFilterPopover(0);
openFirstAlert();
waitForAlerts();
});
it(`URL is updated when filters are updated`, () => {
@ -256,7 +272,7 @@ describe('Detections : Page Filters', { testIsolation: false }, () => {
openPageFilterPopover(1);
cy.get(OPTION_SELECTABLE(1, 'high')).should('be.visible');
cy.get(OPTION_SELECTABLE(1, 'high')).click({ force: true });
cy.get(OPTION_SELECTABLE(1, 'high')).click({});
closePageFilterPopover(1);
});
@ -265,7 +281,7 @@ describe('Detections : Page Filters', { testIsolation: false }, () => {
cy.visit(ALERTS_URL);
cy.get(OPTION_LIST_VALUES(1)).click();
cy.get(OPTION_SELECTABLE(1, 'high')).should('be.visible');
cy.get(OPTION_SELECTABLE(1, 'high')).click({ force: true });
cy.get(OPTION_SELECTABLE(1, 'high')).click({});
// high should be scuccessfully selected.
cy.get(OPTION_LIST_VALUES(1)).contains('high');
@ -312,6 +328,58 @@ describe('Detections : Page Filters', { testIsolation: false }, () => {
cy.get(FILTER_GROUP_CHANGED_BANNER).should('not.exist');
});
context('Impact of inputs', () => {
afterEach(() => {
resetFilters();
});
it.skip('should recover from invalid kql Query result', () => {
// do an invalid search
//
kqlSearch('\\');
cy.get(ALERTS_REFRESH_BTN).trigger('click');
cy.get(TOASTER).should('contain.text', 'KQLSyntaxError');
waitForPageFilters();
togglePageFilterPopover(0);
cy.get(OPTION_SELECTABLE(0, 'open')).should('be.visible');
cy.get(OPTION_SELECTABLE(0, 'open')).should('contain.text', 'open');
cy.get(OPTION_SELECTABLE(0, 'open')).get(OPTION_SELECTABLE_COUNT).should('have.text', 2);
});
it('should take kqlQuery into account', () => {
kqlSearch('kibana.alert.workflow_status: "nothing"');
cy.get(ALERTS_REFRESH_BTN).trigger('click');
waitForPageFilters();
togglePageFilterPopover(0);
cy.get(CONTROL_POPOVER(0)).should('contain.text', 'No options found');
cy.get(OPTION_IGNORED(0, 'open')).should('be.visible');
});
it('should take filters into account', () => {
openAddFilterPopover();
fillAddFilterForm({
key: 'kibana.alert.workflow_status',
value: 'invalid',
});
waitForPageFilters();
togglePageFilterPopover(0);
cy.get(CONTROL_POPOVER(0)).should('contain.text', 'No options found');
cy.get(OPTION_IGNORED(0, 'open')).should('be.visible');
});
it('should take timeRange into account', () => {
const startDateWithZeroAlerts = 'Jan 1, 2002 @ 00:00:00.000';
const endDateWithZeroAlerts = 'Jan 1, 2010 @ 00:00:00.000';
setStartDate(startDateWithZeroAlerts);
setEndDate(endDateWithZeroAlerts);
cy.get(ALERTS_REFRESH_BTN).trigger('click');
waitForPageFilters();
togglePageFilterPopover(0);
cy.get(CONTROL_POPOVER(0)).should('contain.text', 'No options found');
cy.get(OPTION_IGNORED(0, 'open')).should('be.visible');
});
});
it('Number fields are not visible in field edit panel', () => {
const idx = 3;
const { FILTER_FIELD_TYPE, FIELD_TYPES } = FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS;

View file

@ -36,6 +36,12 @@ export const OPTION_SELECTABLE = (popoverIndex: number, value: string) =>
export const OPTION_IGNORED = (popoverIndex: number, value: string) =>
`#control-popover-${popoverIndex} [data-test-subj="optionsList-control-ignored-selection-${value}"]`;
export const OPTION_SELECTABLE_COUNT = getDataTestSubjectSelector(
'optionsList-document-count-badge'
);
export const CONTROL_POPOVER = (popoverIdx: number) => `#control-popover-${popoverIdx}`;
export const DETECTION_PAGE_FILTER_GROUP_WRAPPER = '.filter-group__wrapper';
export const DETECTION_PAGE_FILTERS_LOADING = '.securityPageWrapper .controlFrame--controlLoading';

View file

@ -33,6 +33,8 @@ const mockedDataViewServiceGetter = jest.fn(() => {
} as unknown as DataView);
});
const mockDataViewCreator = jest.fn();
const getKibanaServiceWithMockedGetter = (
mockedDataViewGetter: DataViewsServicePublic['get'] = mockedDataViewServiceGetter
) => {
@ -42,6 +44,7 @@ const getKibanaServiceWithMockedGetter = (
...basicKibanaServicesMock.dataViews,
clearInstanceCache: jest.fn(),
get: mockedDataViewGetter,
create: mockDataViewCreator,
},
};
};
@ -55,7 +58,6 @@ const TestComponent = (props: Partial<ComponentProps<typeof DetectionPageFilterS
<TestProviders>
<DetectionPageFilterSet
chainingSystem="HIERARCHICAL"
dataViewId=""
onFilterChange={onFilterChangeMock}
{...props}
/>
@ -84,29 +86,32 @@ describe('Detection Page Filters', () => {
});
});
it('should check all the fields till any absent field is found', async () => {
it('should create dataview on render', async () => {
render(<TestComponent />);
expect(screen.getByTestId(TEST_IDS.FILTER_LOADING)).toBeInTheDocument();
await waitFor(() => {
expect(getFieldByNameMock).toHaveBeenCalledTimes(4);
expect(kibanaServiceDefaultMock.dataViews.clearInstanceCache).toHaveBeenCalledTimes(0);
expect(mockDataViewCreator).toHaveBeenCalledTimes(1);
expect(mockDataViewCreator).toHaveBeenCalledWith(
expect.objectContaining({
id: 'security_solution_alerts_dv',
name: 'Security Solution Alerts DataView',
allowNoIndex: true,
title: '.siem-signals-spacename',
})
);
});
});
it('should stop checking fields if blank field is found and clear the cache', async () => {
const getFieldByNameLocalMock = jest.fn(() => false);
const mockGetter = jest.fn(() =>
Promise.resolve({ getFieldByName: getFieldByNameLocalMock } as unknown as DataView)
);
const modifiedKibanaServicesMock = getKibanaServiceWithMockedGetter(mockGetter);
(useKibana as jest.Mock).mockReturnValueOnce({ services: modifiedKibanaServicesMock });
it('should clear cache on unmount', async () => {
const { unmount } = render(<TestComponent />);
render(<TestComponent />);
expect(screen.getByTestId(TEST_IDS.FILTER_LOADING)).toBeInTheDocument();
await waitFor(() => {
expect(getFieldByNameLocalMock).toHaveBeenCalledTimes(1);
expect(modifiedKibanaServicesMock.dataViews.clearInstanceCache).toHaveBeenCalledTimes(1);
expect(screen.getByTestId(TEST_IDS.MOCKED_CONTROL)).toBeInTheDocument();
// wait for the document to completely load.
unmount();
});
await waitFor(() => {
expect(kibanaServiceDefaultMock.dataViews.clearInstanceCache).toHaveBeenCalledTimes(1);
});
});

View file

@ -10,15 +10,30 @@ import React, { useEffect, useState, useCallback } from 'react';
import type { Filter } from '@kbn/es-query';
import { isEqual } from 'lodash';
import { EuiFlexItem } from '@elastic/eui';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
import { FilterGroupLoading } from '../../../common/components/filter_group/loading';
import { useKibana } from '../../../common/lib/kibana';
import { DEFAULT_DETECTION_PAGE_FILTERS } from '../../../../common/constants';
import { FilterGroup } from '../../../common/components/filter_group';
import { useSourcererDataView } from '../../../common/containers/sourcerer';
type FilterItemSetProps = Omit<ComponentProps<typeof FilterGroup>, 'initialControls'>;
type FilterItemSetProps = Omit<
ComponentProps<typeof FilterGroup>,
'initialControls' | 'dataViewId'
>;
const SECURITY_ALERT_DATA_VIEW = {
id: 'security_solution_alerts_dv',
name: 'Security Solution Alerts DataView',
};
const FilterItemSetComponent = (props: FilterItemSetProps) => {
const { dataViewId, onFilterChange, ...restFilterItemGroupProps } = props;
const { onFilterChange, ...restFilterItemGroupProps } = props;
const {
indexPattern: { title },
dataViewId,
} = useSourcererDataView(SourcererScopeName.detections);
const [loadingPageFilters, setLoadingPageFilters] = useState(true);
@ -27,24 +42,21 @@ const FilterItemSetComponent = (props: FilterItemSetProps) => {
} = useKibana();
useEffect(() => {
// this makes sure, that if fields are not present in existing copy of the
// dataView, clear the cache before filter group is loaded. This is only
// applicable to `alert` page as new alert mappings are added when first alert
// is encountered
(async () => {
const dataView = await dataViewService.get(dataViewId ?? '');
if (!dataView) return;
for (const filter of DEFAULT_DETECTION_PAGE_FILTERS) {
const fieldExists = dataView.getFieldByName(filter.fieldName);
if (!fieldExists) {
dataViewService.clearInstanceCache(dataViewId ?? '');
setLoadingPageFilters(false);
return;
}
}
// creates an adhoc dataview if it does not already exists just for alert index
const { timeFieldName = '@timestamp' } = await dataViewService.get(dataViewId ?? '');
await dataViewService.create({
id: SECURITY_ALERT_DATA_VIEW.id,
name: SECURITY_ALERT_DATA_VIEW.name,
title,
allowNoIndex: true,
timeFieldName,
});
setLoadingPageFilters(false);
})();
}, [dataViewId, dataViewService]);
return () => dataViewService.clearInstanceCache();
}, [title, dataViewService, dataViewId]);
const [initialFilterControls] = useState(DEFAULT_DETECTION_PAGE_FILTERS);
@ -78,7 +90,7 @@ const FilterItemSetComponent = (props: FilterItemSetProps) => {
return (
<FilterGroup
dataViewId={dataViewId}
dataViewId={SECURITY_ALERT_DATA_VIEW.id}
onFilterChange={filterChangesHandler}
initialControls={initialFilterControls}
{...restFilterItemGroupProps}

View file

@ -153,7 +153,6 @@ const DetectionEnginePageComponent: React.FC<DetectionEngineComponentProps> = ({
const {
indexPattern,
runtimeMappings,
dataViewId,
loading: isLoadingIndexPattern,
} = useSourcererDataView(SourcererScopeName.detections);
@ -351,7 +350,6 @@ const DetectionEnginePageComponent: React.FC<DetectionEngineComponentProps> = ({
</EuiFlexGroup>
) : (
<DetectionPageFilterSet
dataViewId={dataViewId}
onFilterChange={pageFiltersUpdateHandler}
filters={topLevelFilters}
query={query}
@ -367,7 +365,6 @@ const DetectionEnginePageComponent: React.FC<DetectionEngineComponentProps> = ({
[
topLevelFilters,
arePageFiltersEnabled,
dataViewId,
statusFilter,
onFilterGroupChangedCallback,
pageFiltersUpdateHandler,