From ef18d5408d627dcab7134c54c35cd194cfb88d11 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 29 Jul 2021 16:43:48 -0600 Subject: [PATCH 1/9] moves status filter and propogates thru histogram --- .../alerts_filter_group/index.tsx | 3 + .../components/alerts_table/index.tsx | 26 +------ .../detection_engine/detection_engine.tsx | 75 ++++++++++++++++--- 3 files changed, 71 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx index db918951b8555..9bc72c48098a3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx @@ -43,6 +43,7 @@ const AlertsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged hasActiveFilters={filterGroup === FILTER_OPEN} onClick={onClickOpenFilterCallback} withNext + color="primary" > {i18n.OPEN_ALERTS} @@ -52,6 +53,7 @@ const AlertsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged hasActiveFilters={filterGroup === FILTER_IN_PROGRESS} onClick={onClickInProgressFilterCallback} withNext + color="primary" > {i18n.IN_PROGRESS_ALERTS} @@ -60,6 +62,7 @@ const AlertsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged data-test-subj="closedAlerts" hasActiveFilters={filterGroup === FILTER_CLOSED} onClick={onClickCloseFilterCallback} + color="primary" > {i18n.CLOSED_ALERTS} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index a1f2025c6c0d5..c55754181e43a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -31,7 +31,6 @@ import { alertsDefaultModelRuleRegistry, buildAlertStatusFilterRuleRegistry, } from './default_config'; -import { FILTER_OPEN, AlertsTableFilterGroup } from './alerts_filter_group'; import { AlertsUtilityBar } from './alerts_utility_bar'; import * as i18nCommon from '../../../common/translations'; import * as i18n from './translations'; @@ -68,13 +67,12 @@ interface OwnProps { showOnlyThreatIndicatorAlerts: boolean; timelineId: TimelineIdLiteral; to: string; + filterGroup: Status; } type AlertsTableComponentProps = OwnProps & PropsFromRedux; export const AlertsTableComponent: React.FC = ({ - clearEventsDeleted, - clearEventsLoading, clearSelected, defaultFilters, from, @@ -95,10 +93,10 @@ export const AlertsTableComponent: React.FC = ({ showOnlyThreatIndicatorAlerts, timelineId, to, + filterGroup, }) => { const dispatch = useDispatch(); const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); - const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const { browserFields, indexPattern: indexPatterns, @@ -216,17 +214,6 @@ export const AlertsTableComponent: React.FC = ({ } }, [dispatch, isSelectAllChecked, timelineId]); - // Callback for when open/closed filter changes - const onFilterGroupChangedCallback = useCallback( - (newFilterGroup: Status) => { - clearEventsLoading!({ id: timelineId }); - clearEventsDeleted!({ id: timelineId }); - clearSelected!({ id: timelineId }); - setFilterGroup(newFilterGroup); - }, - [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup, timelineId] - ); - // Callback for clearing entire selection from utility bar const clearSelectionCallback = useCallback(() => { clearSelected!({ id: timelineId }); @@ -372,11 +359,6 @@ export const AlertsTableComponent: React.FC = ({ ); }, [dispatch, defaultTimelineModel, filterManager, tGridEnabled, timelineId]); - const headerFilterGroup = useMemo( - () => , - [onFilterGroupChangedCallback] - ); - if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) { return ( @@ -438,8 +420,6 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ eventIds: string[]; isLoading: boolean; }) => dispatch(timelineActions.setEventsLoading({ id, eventIds, isLoading })), - clearEventsLoading: ({ id }: { id: string }) => - dispatch(timelineActions.clearEventsLoading({ id })), setEventsDeleted: ({ id, eventIds, @@ -449,8 +429,6 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ eventIds: string[]; isDeleted: boolean; }) => dispatch(timelineActions.setEventsDeleted({ id, eventIds, isDeleted })), - clearEventsDeleted: ({ id }: { id: string }) => - dispatch(timelineActions.clearEventsDeleted({ id })), }); const connector = connect(makeMapStateToProps, mapDispatchToProps); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index 035784b2e27a4..fc2319ffc0104 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -9,7 +9,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiWindowEvent } from '@elastic/e import styled from 'styled-components'; import { noop } from 'lodash/fp'; import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { useDispatch } from 'react-redux'; +import { connect, ConnectedProps, useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { isTab } from '../../../../../timelines/public'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector'; @@ -44,9 +46,11 @@ import { resetKeyboardFocus, showGlobalFilters, } from '../../../timelines/components/timeline/helpers'; -import { timelineSelectors } from '../../../timelines/store/timeline'; +import { timelineActions, timelineSelectors } from '../../../timelines/store/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { + buildAlertStatusFilter, + buildAlertStatusFilterRuleRegistry, buildShowBuildingBlockFilter, buildShowBuildingBlockFilterRuleRegistry, buildThreatMatchFilter, @@ -58,6 +62,10 @@ import { MissingPrivilegesCallOut } from '../../components/callouts/missing_priv import { useKibana } from '../../../common/lib/kibana'; import { AlertsCountPanel } from '../../components/alerts_kpis/alerts_count_panel'; import { CHART_HEIGHT } from '../../components/alerts_kpis/common/config'; +import { + AlertsTableFilterGroup, + FILTER_OPEN, +} from '../../components/alerts_table/alerts_filter_group'; /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. @@ -68,7 +76,13 @@ const StyledFullHeightContainer = styled.div` flex: 1 1 auto; `; -const DetectionEnginePageComponent = () => { +type DetectionEngineComponentProps = PropsFromRedux; + +const DetectionEnginePageComponent: React.FC = ({ + clearEventsDeleted, + clearEventsLoading, + clearSelected, +}) => { const dispatch = useDispatch(); const containerElement = useRef(null); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); @@ -108,6 +122,7 @@ const DetectionEnginePageComponent = () => { const [showOnlyThreatIndicatorAlerts, setShowOnlyThreatIndicatorAlerts] = useState(false); const loading = userInfoLoading || listsConfigLoading; const { navigateToUrl } = useKibana().services.application; + const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const updateDateRangeCallback = useCallback( ({ x }) => { @@ -134,23 +149,51 @@ const DetectionEnginePageComponent = () => { [formatUrl, navigateToUrl] ); + // Callback for when open/closed filter changes + const onFilterGroupChangedCallback = useCallback( + (newFilterGroup: Status) => { + const timelineId = TimelineId.detectionsPage; + clearEventsLoading!({ id: timelineId }); + clearEventsDeleted!({ id: timelineId }); + clearSelected!({ id: timelineId }); + setFilterGroup(newFilterGroup); + }, + [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup] + ); + const alertsHistogramDefaultFilters = useMemo( () => [ ...filters, ...(ruleRegistryEnabled - ? buildShowBuildingBlockFilterRuleRegistry(showBuildingBlockAlerts) // TODO: Once we are past experimental phase this code should be removed - : buildShowBuildingBlockFilter(showBuildingBlockAlerts)), + ? [ + // TODO: Once we are past experimental phase this code should be removed + ...buildShowBuildingBlockFilterRuleRegistry(showBuildingBlockAlerts), + ...buildAlertStatusFilterRuleRegistry(filterGroup), + ] + : [ + ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), + ...buildAlertStatusFilter(filterGroup), + ]), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), ], - [filters, ruleRegistryEnabled, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] + [ + filters, + ruleRegistryEnabled, + showBuildingBlockAlerts, + showOnlyThreatIndicatorAlerts, + filterGroup, + ] ); // AlertsTable manages global filters itself, so not including `filters` const alertsTableDefaultFilters = useMemo( () => [ ...(ruleRegistryEnabled - ? buildShowBuildingBlockFilterRuleRegistry(showBuildingBlockAlerts) // TODO: Once we are past experimental phase this code should be removed - : buildShowBuildingBlockFilter(showBuildingBlockAlerts)), + ? [ + // TODO: Once we are past experimental phase this code should be removed + ...buildShowBuildingBlockFilterRuleRegistry(showBuildingBlockAlerts), + ] + : [...buildShowBuildingBlockFilter(showBuildingBlockAlerts)]), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), ], [ruleRegistryEnabled, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] @@ -251,6 +294,7 @@ const DetectionEnginePageComponent = () => { {i18n.BUTTON_MANAGE_RULES} + { showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} onShowOnlyThreatIndicatorAlertsChanged={onShowOnlyThreatIndicatorAlertsCallback} to={to} + filterGroup={filterGroup} /> @@ -301,4 +346,16 @@ const DetectionEnginePageComponent = () => { ); }; -export const DetectionEnginePage = React.memo(DetectionEnginePageComponent); +const mapDispatchToProps = (dispatch: Dispatch) => ({ + clearSelected: ({ id }: { id: string }) => dispatch(timelineActions.clearSelected({ id })), + clearEventsLoading: ({ id }: { id: string }) => + dispatch(timelineActions.clearEventsLoading({ id })), + clearEventsDeleted: ({ id }: { id: string }) => + dispatch(timelineActions.clearEventsDeleted({ id })), +}); + +const connector = connect(null, mapDispatchToProps); + +type PropsFromRedux = ConnectedProps; + +export const DetectionEnginePage = connector(React.memo(DetectionEnginePageComponent)); From d1c954e099721d6bd2d36aab99585ffc9d6fb1af Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 29 Jul 2021 17:40:46 -0600 Subject: [PATCH 2/9] fixes spacing --- .../detections/components/alerts_table/index.test.tsx | 2 -- .../detections/components/alerts_table/index.tsx | 4 ++-- .../pages/detection_engine/detection_engine.tsx | 10 +++++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx index be11aecfe47dd..dba7915460ada 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx @@ -35,9 +35,7 @@ describe('AlertsTableComponent', () => { isSelectAllChecked={false} clearSelected={jest.fn()} setEventsLoading={jest.fn()} - clearEventsLoading={jest.fn()} setEventsDeleted={jest.fn()} - clearEventsDeleted={jest.fn()} showBuildingBlockAlerts={false} onShowBuildingBlockAlertsChanged={jest.fn()} showOnlyThreatIndicatorAlerts={false} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index c55754181e43a..5b57cfee3472f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -67,7 +67,7 @@ interface OwnProps { showOnlyThreatIndicatorAlerts: boolean; timelineId: TimelineIdLiteral; to: string; - filterGroup: Status; + filterGroup?: Status; } type AlertsTableComponentProps = OwnProps & PropsFromRedux; @@ -93,7 +93,7 @@ export const AlertsTableComponent: React.FC = ({ showOnlyThreatIndicatorAlerts, timelineId, to, - filterGroup, + filterGroup = 'open', }) => { const dispatch = useDispatch(); const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index fc2319ffc0104..d509b4e1abd21 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -5,7 +5,13 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiWindowEvent } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiWindowEvent, + EuiHorizontalRule, +} from '@elastic/eui'; import styled from 'styled-components'; import { noop } from 'lodash/fp'; import React, { useCallback, useMemo, useRef, useState } from 'react'; @@ -294,7 +300,9 @@ const DetectionEnginePageComponent: React.FC = ({ {i18n.BUTTON_MANAGE_RULES} + + Date: Wed, 4 Aug 2021 15:50:21 -0400 Subject: [PATCH 3/9] fixes color --- .../alerts_filter_group/index.tsx | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx index 9bc72c48098a3..8529004a8c9a7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx @@ -6,7 +6,9 @@ */ import { EuiFilterButton, EuiFilterGroup } from '@elastic/eui'; +import { rgba } from 'polished'; import React, { useCallback, useState } from 'react'; +import styled from 'styled-components'; import { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; import * as i18n from '../translations'; @@ -14,6 +16,22 @@ export const FILTER_OPEN: Status = 'open'; export const FILTER_CLOSED: Status = 'closed'; export const FILTER_IN_PROGRESS: Status = 'in-progress'; +const StatusFilterButton = styled(EuiFilterButton)<{ isActive: boolean }>` + ${(props) => + props.isActive + ? ` + background: ${props.theme.eui.euiColorPrimary}; + ` + : ''} +`; + +const StatusFilterGroup = styled(EuiFilterGroup)` + background: ${({ theme }) => rgba(theme.eui.euiColorPrimary, 0.2)}; + .euiButtonEmpty--ghost:enabled:focus { + background-color: ${({ theme }) => theme.eui.euiColorPrimary}; + } +`; + interface Props { onFilterGroupChanged: (filterGroup: Status) => void; } @@ -37,36 +55,39 @@ const AlertsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged }, [setFilterGroup, onFilterGroupChanged]); return ( - - + {i18n.OPEN_ALERTS} - + - {i18n.IN_PROGRESS_ALERTS} - + - {i18n.CLOSED_ALERTS} - - + + ); }; From 3fa23bbae27da4d4e90348796a59e4ded3ecb02e Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 4 Aug 2021 17:43:03 -0400 Subject: [PATCH 4/9] adds tests --- .../alerts_histogram_panel/index.test.tsx | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 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 0d6793eb2b886..484cd66575005 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 @@ -9,7 +9,7 @@ import React from 'react'; import { waitFor, act } from '@testing-library/react'; import { mount } from 'enzyme'; -import { esQuery } from '../../../../../../../../src/plugins/data/public'; +import { esQuery, Filter } from '../../../../../../../../src/plugins/data/public'; import { TestProviders } from '../../../../common/mock'; import { SecurityPageName } from '../../../../app/types'; @@ -78,6 +78,11 @@ describe('AlertsHistogramPanel', () => { updateDateRange: jest.fn(), }; + afterEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + it('renders correctly', () => { const wrapper = mount( @@ -157,7 +162,7 @@ describe('AlertsHistogramPanel', () => { combinedQueries: '{"bool":{"must":[],"filter":[{"match_all":{}},{"exists":{"field":"process.name"}}],"should":[],"must_not":[]}}', }; - mount( + const wrapper = mount( @@ -180,6 +185,60 @@ describe('AlertsHistogramPanel', () => { ], ]); }); + wrapper.unmount(); + }); + }); + + describe('Filters', () => { + it('filters props is valid, alerts query include filter', async () => { + const mockGetAlertsHistogramQuery = jest.spyOn(helpers, 'getAlertsHistogramQuery'); + const statusFilter: Filter = { + meta: { + alias: null, + disabled: false, + key: 'signal.status', + negate: false, + params: { + query: 'open', + }, + type: 'phrase', + }, + query: { + term: { + 'signal.status': 'open', + }, + }, + }; + + const props = { + ...defaultProps, + query: { query: '', language: 'kql' }, + filters: [statusFilter], + }; + const wrapper = mount( + + + + ); + + await waitFor(() => { + expect(mockGetAlertsHistogramQuery.mock.calls[1]).toEqual([ + 'signal.rule.name', + '2020-07-07T08:20:18.966Z', + '2020-07-08T08:20:18.966Z', + [ + { + bool: { + filter: [{ term: { 'signal.status': 'open' } }], + must: [], + must_not: [], + should: [], + }, + }, + ], + ]); + }); + wrapper.unmount(); }); }); From c3f634bb7cf36e801f3ef0682c3854ce35ba2eac Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 4 Aug 2021 17:56:35 -0400 Subject: [PATCH 5/9] fixes merge conflicts --- .../public/detections/components/alerts_table/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 5b57cfee3472f..40b6a21ea3e68 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -375,7 +375,6 @@ export const AlertsTableComponent: React.FC = ({ defaultModel={defaultTimelineModel} end={to} currentFilter={filterGroup} - headerFilterGroup={headerFilterGroup} id={timelineId} onRuleChange={onRuleChange} renderCellValue={RenderCellValue} From d33c233244a78eb1926ab1bffdb5d640309f05b6 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 9 Aug 2021 14:59:44 -0400 Subject: [PATCH 6/9] adds status filter changes to rule details page --- .../detection_engine/rules/details/index.tsx | 67 +++++++++++++++++-- .../test/security_solution_cypress/runner.ts | 2 +- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 233189a3e8be9..b654d02dd6997 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -23,7 +23,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { noop } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useParams } from 'react-router-dom'; -import { useDispatch } from 'react-redux'; +import { connect, ConnectedProps, useDispatch } from 'react-redux'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; import { @@ -31,6 +31,7 @@ import { ExceptionListIdentifiers, } from '@kbn/securitysolution-io-ts-list-types'; +import { Dispatch } from 'redux'; import { isTab } from '../../../../../../../timelines/public'; import { useDeepEqualSelector, @@ -63,6 +64,8 @@ import { StepDefineRule } from '../../../../components/rules/step_define_rule'; import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule'; import { buildAlertsRuleIdFilter, + buildAlertStatusFilter, + buildAlertStatusFilterRuleRegistry, buildShowBuildingBlockFilter, buildShowBuildingBlockFilterRuleRegistry, buildThreatMatchFilter, @@ -98,7 +101,7 @@ import { resetKeyboardFocus, showGlobalFilters, } from '../../../../../timelines/components/timeline/helpers'; -import { timelineSelectors } from '../../../../../timelines/store/timeline'; +import { timelineActions, timelineSelectors } from '../../../../../timelines/store/timeline'; import { timelineDefaults } from '../../../../../timelines/store/timeline/defaults'; import { useSourcererScope } from '../../../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; @@ -118,6 +121,11 @@ import { MissingPrivilegesCallOut } from '../../../../components/callouts/missin import { useRuleWithFallback } from '../../../../containers/detection_engine/rules/use_rule_with_fallback'; import { BadgeOptions } from '../../../../../common/components/header_page/types'; import { AlertsStackByField } from '../../../../components/alerts_kpis/common/types'; +import { Status } from '../../../../../../common/detection_engine/schemas/common/schemas'; +import { + AlertsTableFilterGroup, + FILTER_OPEN, +} from '../../../../components/alerts_table/alerts_filter_group'; /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. @@ -155,7 +163,13 @@ const ruleDetailTabs = [ }, ]; -const RuleDetailsPageComponent = () => { +type DetectionEngineComponentProps = PropsFromRedux; + +const RuleDetailsPageComponent: React.FC = ({ + clearEventsDeleted, + clearEventsLoading, + clearSelected, +}) => { const { navigateToApp } = useKibana().services.application; const dispatch = useDispatch(); const containerElement = useRef(null); @@ -226,6 +240,7 @@ const RuleDetailsPageComponent = () => { const mlCapabilities = useMlCapabilities(); const { formatUrl } = useFormatUrl(SecurityPageName.rules); const { globalFullScreen } = useGlobalFullScreen(); + const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); // TODO: Once we are past experimental phase this code should be removed const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled'); @@ -315,6 +330,18 @@ const RuleDetailsPageComponent = () => { [rule, ruleLoading] ); + // Callback for when open/closed filter changes + const onFilterGroupChangedCallback = useCallback( + (newFilterGroup: Status) => { + const timelineId = TimelineId.detectionsPage; + clearEventsLoading!({ id: timelineId }); + clearEventsDeleted!({ id: timelineId }); + clearSelected!({ id: timelineId }); + setFilterGroup(newFilterGroup); + }, + [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup] + ); + // Set showBuildingBlockAlerts if rule is a Building Block Rule otherwise we won't show alerts useEffect(() => { setShowBuildingBlockAlerts(rule?.building_block_type != null); @@ -324,11 +351,23 @@ const RuleDetailsPageComponent = () => { () => [ ...buildAlertsRuleIdFilter(ruleId), ...(ruleRegistryEnabled - ? buildShowBuildingBlockFilterRuleRegistry(showBuildingBlockAlerts) // TODO: Once we are past experimental phase this code should be removed - : buildShowBuildingBlockFilter(showBuildingBlockAlerts)), + ? [ + ...buildShowBuildingBlockFilterRuleRegistry(showBuildingBlockAlerts), // TODO: Once we are past experimental phase this code should be removed + ...buildAlertStatusFilterRuleRegistry(filterGroup), + ] + : [ + ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), + ...buildAlertStatusFilter(filterGroup), + ]), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), ], - [ruleId, ruleRegistryEnabled, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] + [ + ruleId, + ruleRegistryEnabled, + showBuildingBlockAlerts, + showOnlyThreatIndicatorAlerts, + filterGroup, + ] ); const alertMergedFilters = useMemo(() => [...alertDefaultFilters, ...filters], [ @@ -705,6 +744,8 @@ const RuleDetailsPageComponent = () => { {ruleDetailTab === RuleDetailTabs.alerts && ( <> + + { ); }; +const mapDispatchToProps = (dispatch: Dispatch) => ({ + clearSelected: ({ id }: { id: string }) => dispatch(timelineActions.clearSelected({ id })), + clearEventsLoading: ({ id }: { id: string }) => + dispatch(timelineActions.clearEventsLoading({ id })), + clearEventsDeleted: ({ id }: { id: string }) => + dispatch(timelineActions.clearEventsDeleted({ id })), +}); + +const connector = connect(null, mapDispatchToProps); + +type PropsFromRedux = ConnectedProps; + RuleDetailsPageComponent.displayName = 'RuleDetailsPageComponent'; -export const RuleDetailsPage = React.memo(RuleDetailsPageComponent); +export const RuleDetailsPage = connector(React.memo(RuleDetailsPageComponent)); RuleDetailsPage.displayName = 'RuleDetailsPage'; diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index 10a754d9bc53f..0aad166c14b78 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -22,7 +22,7 @@ export async function SecuritySolutionCypressCliTestRunner({ getService }: FtrPr await withProcRunner(log, async (procs) => { await procs.run('cypress', { cmd: 'yarn', - args: ['cypress:run'], + args: ['cypress:open'], cwd: resolve(__dirname, '../../plugins/security_solution'), env: { FORCE_COLOR: '1', From 2278ac331537ebde157a4cd28e7500a68ead70e4 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 9 Aug 2021 16:37:36 -0400 Subject: [PATCH 7/9] reverts cypress dev change --- x-pack/test/security_solution_cypress/runner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index 0aad166c14b78..10a754d9bc53f 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -22,7 +22,7 @@ export async function SecuritySolutionCypressCliTestRunner({ getService }: FtrPr await withProcRunner(log, async (procs) => { await procs.run('cypress', { cmd: 'yarn', - args: ['cypress:open'], + args: ['cypress:run'], cwd: resolve(__dirname, '../../plugins/security_solution'), env: { FORCE_COLOR: '1', From 33e816573cf1ba7cc4810e7e839d54019577e475 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 9 Aug 2021 16:49:56 -0400 Subject: [PATCH 8/9] addresses comments --- .../components/alerts_table/alerts_filter_group/index.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx index 8529004a8c9a7..82f58b5b1c723 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_filter_group/index.tsx @@ -17,12 +17,7 @@ export const FILTER_CLOSED: Status = 'closed'; export const FILTER_IN_PROGRESS: Status = 'in-progress'; const StatusFilterButton = styled(EuiFilterButton)<{ isActive: boolean }>` - ${(props) => - props.isActive - ? ` - background: ${props.theme.eui.euiColorPrimary}; - ` - : ''} + background: ${({ isActive, theme }) => (isActive ? theme.eui.euiColorPrimary : '')}; `; const StatusFilterGroup = styled(EuiFilterGroup)` From 76daf066909474a562a0f427fb27fd4fde00de4a Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 9 Aug 2021 20:40:07 -0400 Subject: [PATCH 9/9] fixes cypress tests --- .../detection_engine/rules/details/index.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index b654d02dd6997..230eaeb10939d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -370,6 +370,21 @@ const RuleDetailsPageComponent: React.FC = ({ ] ); + const alertsTableDefaultFilters = useMemo( + () => [ + ...buildAlertsRuleIdFilter(ruleId), + ...filters, + ...(ruleRegistryEnabled + ? [ + // TODO: Once we are past experimental phase this code should be removed + ...buildShowBuildingBlockFilterRuleRegistry(showBuildingBlockAlerts), + ] + : [...buildShowBuildingBlockFilter(showBuildingBlockAlerts)]), + ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), + ], + [ruleId, filters, ruleRegistryEnabled, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] + ); + const alertMergedFilters = useMemo(() => [...alertDefaultFilters, ...filters], [ alertDefaultFilters, filters, @@ -758,8 +773,9 @@ const RuleDetailsPageComponent: React.FC = ({ {ruleId != null && (