mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Detection Page] Status filter refactor (#107249)
This commit is contained in:
parent
4f535c6621
commit
6b29ca1ce8
6 changed files with 242 additions and 55 deletions
|
@ -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(
|
||||
<TestProviders>
|
||||
|
@ -157,7 +162,7 @@ describe('AlertsHistogramPanel', () => {
|
|||
combinedQueries:
|
||||
'{"bool":{"must":[],"filter":[{"match_all":{}},{"exists":{"field":"process.name"}}],"should":[],"must_not":[]}}',
|
||||
};
|
||||
mount(
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<AlertsHistogramPanel {...props} />
|
||||
</TestProviders>
|
||||
|
@ -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(
|
||||
<TestProviders>
|
||||
<AlertsHistogramPanel {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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,17 @@ 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 }>`
|
||||
background: ${({ isActive, theme }) => (isActive ? 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,33 +50,39 @@ const AlertsTableFilterGroupComponent: React.FC<Props> = ({ onFilterGroupChanged
|
|||
}, [setFilterGroup, onFilterGroupChanged]);
|
||||
|
||||
return (
|
||||
<EuiFilterGroup data-test-subj="alerts-table-filter-group">
|
||||
<EuiFilterButton
|
||||
<StatusFilterGroup data-test-subj="alerts-table-filter-group">
|
||||
<StatusFilterButton
|
||||
data-test-subj="openAlerts"
|
||||
hasActiveFilters={filterGroup === FILTER_OPEN}
|
||||
isActive={filterGroup === FILTER_OPEN}
|
||||
onClick={onClickOpenFilterCallback}
|
||||
withNext
|
||||
color={filterGroup === FILTER_OPEN ? 'ghost' : 'primary'}
|
||||
>
|
||||
{i18n.OPEN_ALERTS}
|
||||
</EuiFilterButton>
|
||||
</StatusFilterButton>
|
||||
|
||||
<EuiFilterButton
|
||||
<StatusFilterButton
|
||||
data-test-subj="inProgressAlerts"
|
||||
hasActiveFilters={filterGroup === FILTER_IN_PROGRESS}
|
||||
isActive={filterGroup === FILTER_IN_PROGRESS}
|
||||
onClick={onClickInProgressFilterCallback}
|
||||
withNext
|
||||
color={filterGroup === FILTER_IN_PROGRESS ? 'ghost' : 'primary'}
|
||||
>
|
||||
{i18n.IN_PROGRESS_ALERTS}
|
||||
</EuiFilterButton>
|
||||
</StatusFilterButton>
|
||||
|
||||
<EuiFilterButton
|
||||
<StatusFilterButton
|
||||
data-test-subj="closedAlerts"
|
||||
hasActiveFilters={filterGroup === FILTER_CLOSED}
|
||||
isActive={filterGroup === FILTER_CLOSED}
|
||||
onClick={onClickCloseFilterCallback}
|
||||
color={filterGroup === FILTER_CLOSED ? 'ghost' : 'primary'}
|
||||
>
|
||||
{i18n.CLOSED_ALERTS}
|
||||
</EuiFilterButton>
|
||||
</EuiFilterGroup>
|
||||
</StatusFilterButton>
|
||||
</StatusFilterGroup>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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<AlertsTableComponentProps> = ({
|
||||
clearEventsDeleted,
|
||||
clearEventsLoading,
|
||||
clearSelected,
|
||||
defaultFilters,
|
||||
from,
|
||||
|
@ -95,10 +93,10 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
showOnlyThreatIndicatorAlerts,
|
||||
timelineId,
|
||||
to,
|
||||
filterGroup = 'open',
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [showClearSelectionAction, setShowClearSelectionAction] = useState(false);
|
||||
const [filterGroup, setFilterGroup] = useState<Status>(FILTER_OPEN);
|
||||
const {
|
||||
browserFields,
|
||||
indexPattern: indexPatterns,
|
||||
|
@ -216,17 +214,6 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
}
|
||||
}, [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<AlertsTableComponentProps> = ({
|
|||
);
|
||||
}, [dispatch, defaultTimelineModel, filterManager, tGridEnabled, timelineId]);
|
||||
|
||||
const headerFilterGroup = useMemo(
|
||||
() => <AlertsTableFilterGroup onFilterGroupChanged={onFilterGroupChangedCallback} />,
|
||||
[onFilterGroupChangedCallback]
|
||||
);
|
||||
|
||||
if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) {
|
||||
return (
|
||||
<EuiPanel hasBorder>
|
||||
|
@ -393,7 +375,6 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
defaultModel={defaultTimelineModel}
|
||||
end={to}
|
||||
currentFilter={filterGroup}
|
||||
headerFilterGroup={headerFilterGroup}
|
||||
id={timelineId}
|
||||
onRuleChange={onRuleChange}
|
||||
renderCellValue={RenderCellValue}
|
||||
|
@ -438,8 +419,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 +428,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);
|
||||
|
|
|
@ -5,11 +5,19 @@
|
|||
* 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';
|
||||
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 +52,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 +68,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 +82,13 @@ const StyledFullHeightContainer = styled.div`
|
|||
flex: 1 1 auto;
|
||||
`;
|
||||
|
||||
const DetectionEnginePageComponent = () => {
|
||||
type DetectionEngineComponentProps = PropsFromRedux;
|
||||
|
||||
const DetectionEnginePageComponent: React.FC<DetectionEngineComponentProps> = ({
|
||||
clearEventsDeleted,
|
||||
clearEventsLoading,
|
||||
clearSelected,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const containerElement = useRef<HTMLDivElement | null>(null);
|
||||
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
|
||||
|
@ -108,6 +128,7 @@ const DetectionEnginePageComponent = () => {
|
|||
const [showOnlyThreatIndicatorAlerts, setShowOnlyThreatIndicatorAlerts] = useState(false);
|
||||
const loading = userInfoLoading || listsConfigLoading;
|
||||
const { navigateToUrl } = useKibana().services.application;
|
||||
const [filterGroup, setFilterGroup] = useState<Status>(FILTER_OPEN);
|
||||
|
||||
const updateDateRangeCallback = useCallback<UpdateDateRange>(
|
||||
({ x }) => {
|
||||
|
@ -134,23 +155,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]
|
||||
|
@ -254,6 +303,9 @@ const DetectionEnginePageComponent = () => {
|
|||
{i18n.BUTTON_MANAGE_RULES}
|
||||
</LinkButton>
|
||||
</DetectionEngineHeaderPage>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<AlertsTableFilterGroup onFilterGroupChanged={onFilterGroupChangedCallback} />
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup wrap>
|
||||
<EuiFlexItem grow={2}>
|
||||
<AlertsHistogramPanel
|
||||
|
@ -291,6 +343,7 @@ const DetectionEnginePageComponent = () => {
|
|||
showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts}
|
||||
onShowOnlyThreatIndicatorAlertsChanged={onShowOnlyThreatIndicatorAlertsCallback}
|
||||
to={to}
|
||||
filterGroup={filterGroup}
|
||||
/>
|
||||
</SecuritySolutionPageWrapper>
|
||||
</StyledFullHeightContainer>
|
||||
|
@ -304,4 +357,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<typeof connector>;
|
||||
|
||||
export const DetectionEnginePage = connector(React.memo(DetectionEnginePageComponent));
|
||||
|
|
|
@ -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<DetectionEngineComponentProps> = ({
|
||||
clearEventsDeleted,
|
||||
clearEventsLoading,
|
||||
clearSelected,
|
||||
}) => {
|
||||
const { navigateToApp } = useKibana().services.application;
|
||||
const dispatch = useDispatch();
|
||||
const containerElement = useRef<HTMLDivElement | null>(null);
|
||||
|
@ -226,6 +240,7 @@ const RuleDetailsPageComponent = () => {
|
|||
const mlCapabilities = useMlCapabilities();
|
||||
const { formatUrl } = useFormatUrl(SecurityPageName.rules);
|
||||
const { globalFullScreen } = useGlobalFullScreen();
|
||||
const [filterGroup, setFilterGroup] = useState<Status>(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,38 @@ 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 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], [
|
||||
|
@ -705,6 +759,8 @@ const RuleDetailsPageComponent = () => {
|
|||
</Display>
|
||||
{ruleDetailTab === RuleDetailTabs.alerts && (
|
||||
<>
|
||||
<AlertsTableFilterGroup onFilterGroupChanged={onFilterGroupChangedCallback} />
|
||||
<EuiSpacer size="m" />
|
||||
<Display show={!globalFullScreen}>
|
||||
<AlertsHistogramPanel
|
||||
filters={alertMergedFilters}
|
||||
|
@ -717,8 +773,9 @@ const RuleDetailsPageComponent = () => {
|
|||
</Display>
|
||||
{ruleId != null && (
|
||||
<AlertsTable
|
||||
filterGroup={filterGroup}
|
||||
timelineId={TimelineId.detectionsRulesDetailsPage}
|
||||
defaultFilters={alertDefaultFilters}
|
||||
defaultFilters={alertsTableDefaultFilters}
|
||||
hasIndexWrite={hasIndexWrite ?? false}
|
||||
hasIndexMaintenance={hasIndexMaintenance ?? false}
|
||||
from={from}
|
||||
|
@ -760,8 +817,20 @@ const RuleDetailsPageComponent = () => {
|
|||
);
|
||||
};
|
||||
|
||||
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<typeof connector>;
|
||||
|
||||
RuleDetailsPageComponent.displayName = 'RuleDetailsPageComponent';
|
||||
|
||||
export const RuleDetailsPage = React.memo(RuleDetailsPageComponent);
|
||||
export const RuleDetailsPage = connector(React.memo(RuleDetailsPageComponent));
|
||||
|
||||
RuleDetailsPage.displayName = 'RuleDetailsPage';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue