mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[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:
parent
3888dc0309
commit
5df083d62f
5 changed files with 132 additions and 44 deletions
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue