mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Expandable flyout - add suppressed alerts to correlations (#164649)
This commit is contained in:
parent
b88235aafe
commit
ef7ea49b1e
34 changed files with 755 additions and 54 deletions
|
@ -97,6 +97,7 @@ const RowActionComponent = ({
|
|||
},
|
||||
};
|
||||
|
||||
// excluding rule preview page as some sections in new flyout are not applicable when user is creating a new rule
|
||||
if (isSecurityFlyoutEnabled && tableId !== TableId.rulePreview) {
|
||||
openFlyout({
|
||||
right: {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { EuiButton, EuiButtonEmpty } from '@elastic/eui';
|
||||
import type { IconType } from '@elastic/eui';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
|
@ -31,11 +32,21 @@ export interface InvestigateInTimelineButtonProps {
|
|||
timeRange?: TimeRange;
|
||||
keepDataView?: boolean;
|
||||
isDisabled?: boolean;
|
||||
iconType?: IconType;
|
||||
}
|
||||
|
||||
export const InvestigateInTimelineButton: React.FunctionComponent<
|
||||
InvestigateInTimelineButtonProps
|
||||
> = ({ asEmptyButton, children, dataProviders, filters, timeRange, keepDataView, ...rest }) => {
|
||||
> = ({
|
||||
asEmptyButton,
|
||||
children,
|
||||
dataProviders,
|
||||
filters,
|
||||
timeRange,
|
||||
keepDataView,
|
||||
iconType,
|
||||
...rest
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const getDataViewsSelector = useMemo(
|
||||
|
@ -113,6 +124,7 @@ export const InvestigateInTimelineButton: React.FunctionComponent<
|
|||
onClick={configureAndOpenTimeline}
|
||||
flush="right"
|
||||
size="xs"
|
||||
iconType={iconType}
|
||||
>
|
||||
{children}
|
||||
</EuiButtonEmpty>
|
||||
|
|
|
@ -7,7 +7,13 @@
|
|||
|
||||
import type { MouseEvent } from 'react';
|
||||
import React from 'react';
|
||||
import { EuiContextMenuItem, EuiButtonIcon, EuiToolTip, EuiText } from '@elastic/eui';
|
||||
import {
|
||||
EuiContextMenuItem,
|
||||
EuiButtonIcon,
|
||||
EuiToolTip,
|
||||
EuiText,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
import { EventsTdContent } from '../../../timelines/components/timeline/styles';
|
||||
import { DEFAULT_ACTION_BUTTON_WIDTH } from '.';
|
||||
|
||||
|
@ -20,7 +26,7 @@ interface ActionIconItemProps {
|
|||
isDisabled?: boolean;
|
||||
onClick?: (event: MouseEvent) => void;
|
||||
children?: React.ReactNode;
|
||||
buttonType?: 'text' | 'icon';
|
||||
buttonType?: 'text' | 'icon' | 'emptyButton';
|
||||
}
|
||||
|
||||
const ActionIconItemComponent: React.FC<ActionIconItemProps> = ({
|
||||
|
@ -67,6 +73,17 @@ const ActionIconItemComponent: React.FC<ActionIconItemProps> = ({
|
|||
</EuiText>
|
||||
</EuiContextMenuItem>
|
||||
)}
|
||||
{buttonType === 'emptyButton' && (
|
||||
<EuiButtonEmpty
|
||||
onClick={onClick}
|
||||
iconType="timeline"
|
||||
flush="right"
|
||||
size="xs"
|
||||
data-test-subj={dataTestSubj}
|
||||
>
|
||||
{content}
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import { useInvestigateInTimeline } from './use_investigate_in_timeline';
|
|||
interface InvestigateInTimelineActionProps {
|
||||
ecsRowData?: Ecs | null;
|
||||
ariaLabel?: string;
|
||||
buttonType?: 'text' | 'icon';
|
||||
buttonType?: 'text' | 'icon' | 'emptyButton';
|
||||
onInvestigateInTimelineAlertClick?: () => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,15 +14,13 @@ import { useShowRelatedAlertsByAncestry } from '../../shared/hooks/use_show_rela
|
|||
import { useShowRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_show_related_alerts_by_same_source_event';
|
||||
import { useShowRelatedAlertsBySession } from '../../shared/hooks/use_show_related_alerts_by_session';
|
||||
import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases';
|
||||
import { useShowSuppressedAlerts } from '../../shared/hooks/use_show_suppressed_alerts';
|
||||
import {
|
||||
CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID,
|
||||
CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID,
|
||||
CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID,
|
||||
CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID,
|
||||
CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID,
|
||||
CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID,
|
||||
CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID,
|
||||
CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID,
|
||||
CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID,
|
||||
CORRELATIONS_DETAILS_TEST_ID,
|
||||
} from './test_ids';
|
||||
import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session';
|
||||
|
@ -30,6 +28,7 @@ import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_re
|
|||
import { useFetchRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_fetch_related_alerts_by_same_source_event';
|
||||
import { useFetchRelatedCases } from '../../shared/hooks/use_fetch_related_cases';
|
||||
import { mockContextValue } from '../mocks/mock_context';
|
||||
import { EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID } from '../../shared/components/test_ids';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
|
@ -39,6 +38,7 @@ jest.mock('../../shared/hooks/use_show_related_alerts_by_ancestry');
|
|||
jest.mock('../../shared/hooks/use_show_related_alerts_by_same_source_event');
|
||||
jest.mock('../../shared/hooks/use_show_related_alerts_by_session');
|
||||
jest.mock('../../shared/hooks/use_show_related_cases');
|
||||
jest.mock('../../shared/hooks/use_show_suppressed_alerts');
|
||||
jest.mock('../../shared/hooks/use_fetch_related_alerts_by_session');
|
||||
jest.mock('../../shared/hooks/use_fetch_related_alerts_by_ancestry');
|
||||
jest.mock('../../shared/hooks/use_fetch_related_alerts_by_same_source_event');
|
||||
|
@ -54,6 +54,11 @@ const renderCorrelationDetails = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_TITLE_TEST_ID =
|
||||
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(
|
||||
CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID
|
||||
);
|
||||
|
||||
describe('CorrelationsDetails', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -70,6 +75,7 @@ describe('CorrelationsDetails', () => {
|
|||
.mocked(useShowRelatedAlertsBySession)
|
||||
.mockReturnValue({ show: true, entityId: 'entityId' });
|
||||
jest.mocked(useShowRelatedCases).mockReturnValue(true);
|
||||
jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: true, alertSuppressionCount: 1 });
|
||||
|
||||
(useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
|
@ -102,6 +108,7 @@ describe('CorrelationsDetails', () => {
|
|||
expect(getByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_TITLE_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render no section and show error message if show values are false', () => {
|
||||
|
@ -115,13 +122,23 @@ describe('CorrelationsDetails', () => {
|
|||
.mocked(useShowRelatedAlertsBySession)
|
||||
.mockReturnValue({ show: false, entityId: 'entityId' });
|
||||
jest.mocked(useShowRelatedCases).mockReturnValue(false);
|
||||
jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: false, alertSuppressionCount: 0 });
|
||||
|
||||
const { getByTestId, queryByTestId } = renderCorrelationDetails();
|
||||
|
||||
expect(queryByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID)
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID)
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID)
|
||||
).not.toBeInTheDocument();
|
||||
expect(queryByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByTestId(CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_TITLE_TEST_ID)
|
||||
).not.toBeInTheDocument();
|
||||
expect(getByTestId(`${CORRELATIONS_DETAILS_TEST_ID}Error`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
@ -130,12 +147,22 @@ describe('CorrelationsDetails', () => {
|
|||
jest.mocked(useShowRelatedAlertsBySameSourceEvent).mockReturnValue({ show: true });
|
||||
jest.mocked(useShowRelatedAlertsBySession).mockReturnValue({ show: true });
|
||||
jest.mocked(useShowRelatedCases).mockReturnValue(false);
|
||||
jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: false, alertSuppressionCount: 0 });
|
||||
|
||||
const { queryByTestId } = renderCorrelationDetails();
|
||||
|
||||
expect(queryByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID)
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID)
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID)
|
||||
).not.toBeInTheDocument();
|
||||
expect(queryByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByTestId(CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_TITLE_TEST_ID)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { CORRELATIONS_ERROR_MESSAGE } from './translations';
|
||||
import { CORRELATIONS_DETAILS_TEST_ID } from './test_ids';
|
||||
import { RelatedAlertsBySession } from './related_alerts_by_session';
|
||||
|
@ -14,11 +14,12 @@ import { RelatedAlertsBySameSourceEvent } from './related_alerts_by_same_source_
|
|||
import { RelatedCases } from './related_cases';
|
||||
import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases';
|
||||
import { useShowRelatedAlertsByAncestry } from '../../shared/hooks/use_show_related_alerts_by_ancestry';
|
||||
|
||||
import { useShowSuppressedAlerts } from '../../shared/hooks/use_show_suppressed_alerts';
|
||||
import { useLeftPanelContext } from '../context';
|
||||
import { useShowRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_show_related_alerts_by_same_source_event';
|
||||
import { useShowRelatedAlertsBySession } from '../../shared/hooks/use_show_related_alerts_by_session';
|
||||
import { RelatedAlertsByAncestry } from './related_alerts_by_ancestry';
|
||||
import { SuppressedAlerts } from './suppressed_alerts';
|
||||
|
||||
export const CORRELATIONS_TAB_ID = 'correlations-details';
|
||||
|
||||
|
@ -43,34 +44,59 @@ export const CorrelationsDetails: React.FC = () => {
|
|||
});
|
||||
const { show: showAlertsBySession, entityId } = useShowRelatedAlertsBySession({ getFieldsData });
|
||||
const showCases = useShowRelatedCases();
|
||||
const { show: showSuppressedAlerts, alertSuppressionCount } = useShowSuppressedAlerts({
|
||||
getFieldsData,
|
||||
});
|
||||
|
||||
const canShowAtLeastOneInsight =
|
||||
showAlertsByAncestry || showSameSourceAlerts || showAlertsBySession || showCases;
|
||||
showAlertsByAncestry ||
|
||||
showSameSourceAlerts ||
|
||||
showAlertsBySession ||
|
||||
showCases ||
|
||||
showSuppressedAlerts;
|
||||
|
||||
return (
|
||||
<>
|
||||
{canShowAtLeastOneInsight ? (
|
||||
<>
|
||||
{showAlertsByAncestry && documentId && indices && (
|
||||
<RelatedAlertsByAncestry documentId={documentId} indices={indices} scopeId={scopeId} />
|
||||
<EuiFlexGroup gutterSize="l" direction="column">
|
||||
{showSuppressedAlerts && (
|
||||
<EuiFlexItem>
|
||||
<SuppressedAlerts
|
||||
alertSuppressionCount={alertSuppressionCount}
|
||||
dataAsNestedObject={dataAsNestedObject}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{showCases && (
|
||||
<EuiFlexItem>
|
||||
<RelatedCases eventId={eventId} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
{showSameSourceAlerts && originalEventId && (
|
||||
<RelatedAlertsBySameSourceEvent originalEventId={originalEventId} scopeId={scopeId} />
|
||||
<EuiFlexItem>
|
||||
<RelatedAlertsBySameSourceEvent
|
||||
originalEventId={originalEventId}
|
||||
scopeId={scopeId}
|
||||
eventId={eventId}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
{showAlertsBySession && entityId && (
|
||||
<RelatedAlertsBySession entityId={entityId} scopeId={scopeId} />
|
||||
<EuiFlexItem>
|
||||
<RelatedAlertsBySession entityId={entityId} scopeId={scopeId} eventId={eventId} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
{showCases && <RelatedCases eventId={eventId} />}
|
||||
</>
|
||||
{showAlertsByAncestry && documentId && indices && (
|
||||
<EuiFlexItem>
|
||||
<RelatedAlertsByAncestry
|
||||
documentId={documentId}
|
||||
indices={indices}
|
||||
scopeId={scopeId}
|
||||
eventId={eventId}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<div data-test-subj={`${CORRELATIONS_DETAILS_TEST_ID}Error`}>
|
||||
{CORRELATIONS_ERROR_MESSAGE}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { EuiBasicTable } from '@elastic/eui';
|
||||
import { CorrelationsDetailsAlertsTable, columns } from './correlations_details_alerts_table';
|
||||
import { usePaginatedAlerts } from '../hooks/use_paginated_alerts';
|
||||
|
@ -17,7 +18,11 @@ jest.mock('@elastic/eui', () => ({
|
|||
EuiBasicTable: jest.fn(() => <div data-testid="mock-euibasictable" />),
|
||||
}));
|
||||
|
||||
describe('AlertsTable', () => {
|
||||
const TEST_ID = 'TEST';
|
||||
const scopeId = 'scopeId';
|
||||
const eventId = 'eventId';
|
||||
|
||||
describe('CorrelationsDetailsAlertsTable', () => {
|
||||
const alertIds = ['id1', 'id2', 'id3'];
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -59,7 +64,19 @@ describe('AlertsTable', () => {
|
|||
});
|
||||
|
||||
it('renders EuiBasicTable with correct props', () => {
|
||||
render(<CorrelationsDetailsAlertsTable title={'title'} loading={false} alertIds={alertIds} />);
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<CorrelationsDetailsAlertsTable
|
||||
title={'title'}
|
||||
loading={false}
|
||||
alertIds={alertIds}
|
||||
scopeId={scopeId}
|
||||
eventId={eventId}
|
||||
data-test-subj={TEST_ID}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId(`${TEST_ID}InvestigateInTimeline`)).toBeInTheDocument();
|
||||
|
||||
expect(jest.mocked(usePaginatedAlerts)).toHaveBeenCalled();
|
||||
|
||||
|
|
|
@ -7,16 +7,21 @@
|
|||
|
||||
import React, { type FC, useMemo, useCallback } from 'react';
|
||||
import { type Criteria, EuiBasicTable, formatDate } from '@elastic/eui';
|
||||
|
||||
import { Severity } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { isRight } from 'fp-ts/lib/Either';
|
||||
import { ALERT_REASON, ALERT_RULE_NAME } from '@kbn/rule-data-utils';
|
||||
import type { DataProvider } from '../../../../common/types';
|
||||
import { SeverityBadge } from '../../../detections/components/rules/severity_badge';
|
||||
import { usePaginatedAlerts } from '../hooks/use_paginated_alerts';
|
||||
import * as i18n from './translations';
|
||||
import { ExpandablePanel } from '../../shared/components/expandable_panel';
|
||||
import { InvestigateInTimelineButton } from '../../../common/components/event_details/table/investigate_in_timeline_button';
|
||||
import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../detections/components/alerts_table/translations';
|
||||
import { getDataProvider } from '../../../common/components/event_details/table/use_action_cell_data_provider';
|
||||
|
||||
export const TIMESTAMP_DATE_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS';
|
||||
const dataProviderLimit = 5;
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
|
@ -60,6 +65,14 @@ export interface CorrelationsDetailsAlertsTableProps {
|
|||
* Ids of alerts to display in the table
|
||||
*/
|
||||
alertIds: string[] | undefined;
|
||||
/**
|
||||
* Maintain backwards compatibility // TODO remove when possible
|
||||
*/
|
||||
scopeId: string;
|
||||
/**
|
||||
* Id of the document
|
||||
*/
|
||||
eventId: string;
|
||||
/**
|
||||
* Data test subject string for testing
|
||||
*/
|
||||
|
@ -73,6 +86,8 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr
|
|||
title,
|
||||
loading,
|
||||
alertIds,
|
||||
scopeId,
|
||||
eventId,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const {
|
||||
|
@ -110,11 +125,35 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr
|
|||
);
|
||||
}, [data]);
|
||||
|
||||
const shouldUseFilters = Boolean(
|
||||
alertIds && alertIds.length && alertIds.length >= dataProviderLimit
|
||||
);
|
||||
const dataProviders = useMemo(
|
||||
() => (shouldUseFilters ? null : getDataProviders(scopeId, eventId, alertIds)),
|
||||
[alertIds, shouldUseFilters, scopeId, eventId]
|
||||
);
|
||||
const filters: Filter[] | null = useMemo(
|
||||
() => (shouldUseFilters ? getFilters(alertIds) : null),
|
||||
[alertIds, shouldUseFilters]
|
||||
);
|
||||
|
||||
return (
|
||||
<ExpandablePanel
|
||||
header={{
|
||||
title,
|
||||
iconType: 'warning',
|
||||
headerContent: (
|
||||
<div data-test-subj={`${dataTestSubj}InvestigateInTimeline`}>
|
||||
<InvestigateInTimelineButton
|
||||
dataProviders={dataProviders}
|
||||
filters={filters}
|
||||
asEmptyButton
|
||||
iconType="timeline"
|
||||
>
|
||||
{ACTION_INVESTIGATE_IN_TIMELINE}
|
||||
</InvestigateInTimelineButton>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
content={{ error }}
|
||||
expand={{
|
||||
|
@ -135,3 +174,45 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr
|
|||
</ExpandablePanel>
|
||||
);
|
||||
};
|
||||
|
||||
const getFilters = (alertIds?: string[]) => {
|
||||
if (alertIds && alertIds.length) {
|
||||
return [
|
||||
{
|
||||
meta: {
|
||||
alias: i18n.CORRELATIONS_DETAILS_TABLE_FILTER,
|
||||
type: 'phrases',
|
||||
key: '_id',
|
||||
params: [...alertIds],
|
||||
negate: false,
|
||||
disabled: false,
|
||||
value: alertIds.join(),
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
should: alertIds.map((id) => {
|
||||
return {
|
||||
match_phrase: {
|
||||
_id: id,
|
||||
},
|
||||
};
|
||||
}),
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getDataProviders = (scopeId: string, eventId: string, alertIds?: string[]) => {
|
||||
if (alertIds && alertIds.length) {
|
||||
return alertIds.reduce<DataProvider[]>((result, alertId, index) => {
|
||||
const id = `${scopeId}-${eventId}-event.id-${index}-${alertId}`;
|
||||
result.push(getDataProvider('_id', id, alertId));
|
||||
return result;
|
||||
}, []);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import {
|
||||
CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID,
|
||||
CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID,
|
||||
|
@ -26,6 +27,7 @@ jest.mock('../hooks/use_paginated_alerts');
|
|||
const documentId = 'documentId';
|
||||
const indices = ['index1'];
|
||||
const scopeId = 'scopeId';
|
||||
const eventId = 'eventId';
|
||||
|
||||
const TOGGLE_ICON = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(
|
||||
CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID
|
||||
|
@ -73,11 +75,21 @@ describe('<RelatedAlertsByAncestry />', () => {
|
|||
});
|
||||
|
||||
const { getByTestId } = render(
|
||||
<RelatedAlertsByAncestry documentId={documentId} indices={indices} scopeId={scopeId} />
|
||||
<TestProviders>
|
||||
<RelatedAlertsByAncestry
|
||||
documentId={documentId}
|
||||
indices={indices}
|
||||
scopeId={scopeId}
|
||||
eventId={eventId}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId(TOGGLE_ICON)).toBeInTheDocument();
|
||||
expect(getByTestId(TITLE_ICON)).toBeInTheDocument();
|
||||
expect(getByTestId(TITLE_TEXT)).toBeInTheDocument();
|
||||
expect(
|
||||
getByTestId(`${CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID}InvestigateInTimeline`)
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
@ -88,7 +100,14 @@ describe('<RelatedAlertsByAncestry />', () => {
|
|||
});
|
||||
|
||||
const { container } = render(
|
||||
<RelatedAlertsByAncestry documentId={documentId} indices={indices} scopeId={scopeId} />
|
||||
<TestProviders>
|
||||
<RelatedAlertsByAncestry
|
||||
documentId={documentId}
|
||||
indices={indices}
|
||||
scopeId={scopeId}
|
||||
eventId={eventId}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
|
|
@ -24,6 +24,10 @@ export interface RelatedAlertsByAncestryProps {
|
|||
* Maintain backwards compatibility // TODO remove when possible
|
||||
*/
|
||||
scopeId: string;
|
||||
/**
|
||||
* Id of the document
|
||||
*/
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,6 +37,7 @@ export const RelatedAlertsByAncestry: React.VFC<RelatedAlertsByAncestryProps> =
|
|||
documentId,
|
||||
indices,
|
||||
scopeId,
|
||||
eventId,
|
||||
}) => {
|
||||
const { loading, error, data, dataCount } = useFetchRelatedAlertsByAncestry({
|
||||
documentId,
|
||||
|
@ -50,6 +55,8 @@ export const RelatedAlertsByAncestry: React.VFC<RelatedAlertsByAncestryProps> =
|
|||
title={title}
|
||||
loading={loading}
|
||||
alertIds={data}
|
||||
scopeId={scopeId}
|
||||
eventId={eventId}
|
||||
data-test-subj={CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import {
|
||||
CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID,
|
||||
CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID,
|
||||
|
@ -25,6 +26,7 @@ jest.mock('../hooks/use_paginated_alerts');
|
|||
|
||||
const originalEventId = 'originalEventId';
|
||||
const scopeId = 'scopeId';
|
||||
const eventId = 'eventId';
|
||||
|
||||
const TOGGLE_ICON = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(
|
||||
CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID
|
||||
|
@ -72,11 +74,20 @@ describe('<RelatedAlertsBySameSourceEvent />', () => {
|
|||
});
|
||||
|
||||
const { getByTestId } = render(
|
||||
<RelatedAlertsBySameSourceEvent originalEventId={originalEventId} scopeId={scopeId} />
|
||||
<TestProviders>
|
||||
<RelatedAlertsBySameSourceEvent
|
||||
originalEventId={originalEventId}
|
||||
scopeId={scopeId}
|
||||
eventId={eventId}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId(TOGGLE_ICON)).toBeInTheDocument();
|
||||
expect(getByTestId(TITLE_ICON)).toBeInTheDocument();
|
||||
expect(getByTestId(TITLE_TEXT)).toBeInTheDocument();
|
||||
expect(
|
||||
getByTestId(`${CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID}InvestigateInTimeline`)
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
@ -87,7 +98,13 @@ describe('<RelatedAlertsBySameSourceEvent />', () => {
|
|||
});
|
||||
|
||||
const { container } = render(
|
||||
<RelatedAlertsBySameSourceEvent originalEventId={originalEventId} scopeId={scopeId} />
|
||||
<TestProviders>
|
||||
<RelatedAlertsBySameSourceEvent
|
||||
originalEventId={originalEventId}
|
||||
scopeId={scopeId}
|
||||
eventId={eventId}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
|
|
@ -20,6 +20,10 @@ export interface RelatedAlertsBySameSourceEventProps {
|
|||
* Maintain backwards compatibility // TODO remove when possible
|
||||
*/
|
||||
scopeId: string;
|
||||
/**
|
||||
* Id of the document
|
||||
*/
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,6 +32,7 @@ export interface RelatedAlertsBySameSourceEventProps {
|
|||
export const RelatedAlertsBySameSourceEvent: React.VFC<RelatedAlertsBySameSourceEventProps> = ({
|
||||
originalEventId,
|
||||
scopeId,
|
||||
eventId,
|
||||
}) => {
|
||||
const { loading, error, data, dataCount } = useFetchRelatedAlertsBySameSourceEvent({
|
||||
originalEventId,
|
||||
|
@ -44,6 +49,8 @@ export const RelatedAlertsBySameSourceEvent: React.VFC<RelatedAlertsBySameSource
|
|||
title={title}
|
||||
loading={loading}
|
||||
alertIds={data}
|
||||
scopeId={scopeId}
|
||||
eventId={eventId}
|
||||
data-test-subj={CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import {
|
||||
CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID,
|
||||
CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID,
|
||||
|
@ -25,6 +26,7 @@ jest.mock('../hooks/use_paginated_alerts');
|
|||
|
||||
const entityId = 'entityId';
|
||||
const scopeId = 'scopeId';
|
||||
const eventId = 'eventId';
|
||||
|
||||
const TOGGLE_ICON = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(
|
||||
CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID
|
||||
|
@ -72,11 +74,16 @@ describe('<RelatedAlertsBySession />', () => {
|
|||
});
|
||||
|
||||
const { getByTestId } = render(
|
||||
<RelatedAlertsBySession entityId={entityId} scopeId={scopeId} />
|
||||
<TestProviders>
|
||||
<RelatedAlertsBySession entityId={entityId} scopeId={scopeId} eventId={eventId} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByTestId(TOGGLE_ICON)).toBeInTheDocument();
|
||||
expect(getByTestId(TITLE_ICON)).toBeInTheDocument();
|
||||
expect(getByTestId(TITLE_TEXT)).toBeInTheDocument();
|
||||
expect(
|
||||
getByTestId(`${CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID}InvestigateInTimeline`)
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
@ -86,7 +93,11 @@ describe('<RelatedAlertsBySession />', () => {
|
|||
error: true,
|
||||
});
|
||||
|
||||
const { container } = render(<RelatedAlertsBySession entityId={entityId} scopeId={scopeId} />);
|
||||
const { container } = render(
|
||||
<TestProviders>
|
||||
<RelatedAlertsBySession entityId={entityId} scopeId={scopeId} eventId={eventId} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,6 +20,10 @@ export interface RelatedAlertsBySessionProps {
|
|||
* Maintain backwards compatibility // TODO remove when possible
|
||||
*/
|
||||
scopeId: string;
|
||||
/**
|
||||
* Id of the document
|
||||
*/
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,6 +32,7 @@ export interface RelatedAlertsBySessionProps {
|
|||
export const RelatedAlertsBySession: React.VFC<RelatedAlertsBySessionProps> = ({
|
||||
entityId,
|
||||
scopeId,
|
||||
eventId,
|
||||
}) => {
|
||||
const { loading, error, data, dataCount } = useFetchRelatedAlertsBySession({
|
||||
entityId,
|
||||
|
@ -44,6 +49,8 @@ export const RelatedAlertsBySession: React.VFC<RelatedAlertsBySessionProps> = ({
|
|||
title={title}
|
||||
loading={loading}
|
||||
alertIds={data}
|
||||
scopeId={scopeId}
|
||||
eventId={eventId}
|
||||
data-test-subj={CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import {
|
||||
CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID,
|
||||
SUPPRESSED_ALERTS_SECTION_TECHNICAL_PREVIEW_TEST_ID,
|
||||
} from './test_ids';
|
||||
import { SuppressedAlerts } from './suppressed_alerts';
|
||||
import {
|
||||
EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID,
|
||||
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID,
|
||||
EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID,
|
||||
} from '../../shared/components/test_ids';
|
||||
import { LeftPanelContext } from '../context';
|
||||
import { mockContextValue } from '../mocks/mock_context';
|
||||
|
||||
const mockDataAsNestedObject = {
|
||||
_id: 'testId',
|
||||
};
|
||||
|
||||
const TOGGLE_ICON = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(
|
||||
CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID
|
||||
);
|
||||
const TITLE_ICON = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(
|
||||
CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID
|
||||
);
|
||||
const TITLE_TEXT = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(
|
||||
CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID
|
||||
);
|
||||
const INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID = `${CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID}InvestigateInTimeline`;
|
||||
|
||||
describe('<SuppressedAlerts />', () => {
|
||||
it('should render zero component correctly', () => {
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<LeftPanelContext.Provider value={mockContextValue}>
|
||||
<SuppressedAlerts alertSuppressionCount={0} dataAsNestedObject={mockDataAsNestedObject} />
|
||||
</LeftPanelContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(getByTestId(TITLE_ICON)).toBeInTheDocument();
|
||||
expect(getByTestId(TITLE_TEXT)).toHaveTextContent('0 suppressed alert');
|
||||
expect(queryByTestId(INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(TOGGLE_ICON)).not.toBeInTheDocument();
|
||||
expect(getByTestId(SUPPRESSED_ALERTS_SECTION_TECHNICAL_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render single component correctly', () => {
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<LeftPanelContext.Provider value={mockContextValue}>
|
||||
<SuppressedAlerts alertSuppressionCount={1} dataAsNestedObject={mockDataAsNestedObject} />
|
||||
</LeftPanelContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(getByTestId(TITLE_ICON)).toBeInTheDocument();
|
||||
expect(getByTestId(TITLE_TEXT)).toHaveTextContent('1 suppressed alert');
|
||||
expect(getByTestId(INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID)).toBeInTheDocument();
|
||||
expect(queryByTestId(TOGGLE_ICON)).not.toBeInTheDocument();
|
||||
expect(getByTestId(SUPPRESSED_ALERTS_SECTION_TECHNICAL_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render multiple component correctly', () => {
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<LeftPanelContext.Provider value={mockContextValue}>
|
||||
<SuppressedAlerts alertSuppressionCount={2} dataAsNestedObject={mockDataAsNestedObject} />
|
||||
</LeftPanelContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(getByTestId(TITLE_ICON)).toBeInTheDocument();
|
||||
expect(getByTestId(TITLE_TEXT)).toHaveTextContent('2 suppressed alerts');
|
||||
expect(getByTestId(INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID)).toBeInTheDocument();
|
||||
expect(queryByTestId(TOGGLE_ICON)).not.toBeInTheDocument();
|
||||
expect(getByTestId(SUPPRESSED_ALERTS_SECTION_TECHNICAL_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
|
||||
import { EuiBetaBadge, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
||||
import { CORRELATIONS_SUPPRESSED_ALERTS } from '../../shared/translations';
|
||||
import { ExpandablePanel } from '../../shared/components/expandable_panel';
|
||||
import {
|
||||
CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID,
|
||||
SUPPRESSED_ALERTS_SECTION_TECHNICAL_PREVIEW_TEST_ID,
|
||||
} from './test_ids';
|
||||
import { SUPPRESSED_ALERTS_COUNT_TECHNICAL_PREVIEW } from '../../../common/components/event_details/insights/translations';
|
||||
import { InvestigateInTimelineAction } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_timeline_action';
|
||||
|
||||
export interface SuppressedAlertsProps {
|
||||
/**
|
||||
* An object with top level fields from the ECS object
|
||||
*/
|
||||
dataAsNestedObject: Ecs | null;
|
||||
/**
|
||||
* Value of the kibana.alert.suppression.doc_count field
|
||||
*/
|
||||
alertSuppressionCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays number of suppressed alerts and investigate in timeline icon
|
||||
*/
|
||||
export const SuppressedAlerts: React.VFC<SuppressedAlertsProps> = ({
|
||||
dataAsNestedObject,
|
||||
alertSuppressionCount,
|
||||
}) => {
|
||||
const title = (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
{`${alertSuppressionCount} ${CORRELATIONS_SUPPRESSED_ALERTS(alertSuppressionCount)}`}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiBetaBadge
|
||||
label={SUPPRESSED_ALERTS_COUNT_TECHNICAL_PREVIEW}
|
||||
style={{ verticalAlign: 'middle' }}
|
||||
size="s"
|
||||
data-test-subj={SUPPRESSED_ALERTS_SECTION_TECHNICAL_PREVIEW_TEST_ID}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
const headerContent = alertSuppressionCount > 0 && (
|
||||
<div
|
||||
data-test-subj={`${CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID}InvestigateInTimeline`}
|
||||
>
|
||||
<InvestigateInTimelineAction ecsRowData={dataAsNestedObject} buttonType={'emptyButton'} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<ExpandablePanel
|
||||
header={{
|
||||
title,
|
||||
iconType: 'layers',
|
||||
headerContent,
|
||||
}}
|
||||
data-test-subj={CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
SuppressedAlerts.displayName = 'SuppressedAlerts';
|
|
@ -73,6 +73,10 @@ export const CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID =
|
|||
`${CORRELATIONS_DETAILS_TEST_ID}CasesSection` as const;
|
||||
export const CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID =
|
||||
`${CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID}Table` as const;
|
||||
export const CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID =
|
||||
`${CORRELATIONS_DETAILS_TEST_ID}SuppressedAlertsSection` as const;
|
||||
export const SUPPRESSED_ALERTS_SECTION_TECHNICAL_PREVIEW_TEST_ID =
|
||||
`${CORRELATIONS_DETAILS_TEST_ID}SuppressedAlertsSectionTechnicalPreview` as const;
|
||||
export const RESPONSE_BASE_TEST_ID = `${PREFIX}Responses` as const;
|
||||
export const RESPONSE_DETAILS_TEST_ID = `${RESPONSE_BASE_TEST_ID}Details` as const;
|
||||
export const RESPONSE_EMPTY_TEST_ID = `${RESPONSE_BASE_TEST_ID}Empty` as const;
|
||||
|
|
|
@ -197,3 +197,10 @@ export const CORRELATIONS_CASE_NAME_COLUMN_TITLE = i18n.translate(
|
|||
defaultMessage: 'Name',
|
||||
}
|
||||
);
|
||||
|
||||
export const CORRELATIONS_DETAILS_TABLE_FILTER = i18n.translate(
|
||||
'xpack.securitySolution.flyout.correlations.correlationsDetailsTableFilter',
|
||||
{
|
||||
defaultMessage: 'Correlations Details Table Alert IDs',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID,
|
||||
INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID,
|
||||
INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID,
|
||||
INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID,
|
||||
INSIGHTS_CORRELATIONS_TEST_ID,
|
||||
SUMMARY_ROW_VALUE_TEST_ID,
|
||||
} from './test_ids';
|
||||
|
@ -25,6 +26,7 @@ import { useShowRelatedAlertsByAncestry } from '../../shared/hooks/use_show_rela
|
|||
import { useShowRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_show_related_alerts_by_same_source_event';
|
||||
import { useShowRelatedAlertsBySession } from '../../shared/hooks/use_show_related_alerts_by_session';
|
||||
import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases';
|
||||
import { useShowSuppressedAlerts } from '../../shared/hooks/use_show_suppressed_alerts';
|
||||
import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry';
|
||||
import { useFetchRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_fetch_related_alerts_by_same_source_event';
|
||||
import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session';
|
||||
|
@ -40,6 +42,7 @@ jest.mock('../../shared/hooks/use_show_related_alerts_by_ancestry');
|
|||
jest.mock('../../shared/hooks/use_show_related_alerts_by_same_source_event');
|
||||
jest.mock('../../shared/hooks/use_show_related_alerts_by_session');
|
||||
jest.mock('../../shared/hooks/use_show_related_cases');
|
||||
jest.mock('../../shared/hooks/use_show_suppressed_alerts');
|
||||
jest.mock('../../shared/hooks/use_fetch_related_alerts_by_session');
|
||||
jest.mock('../../shared/hooks/use_fetch_related_alerts_by_ancestry');
|
||||
jest.mock('../../shared/hooks/use_fetch_related_alerts_by_same_source_event');
|
||||
|
@ -56,6 +59,9 @@ const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(
|
|||
INSIGHTS_CORRELATIONS_TEST_ID
|
||||
);
|
||||
|
||||
const SUPPRESSED_ALERTS_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(
|
||||
INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID
|
||||
);
|
||||
const RELATED_ALERTS_BY_ANCESTRY_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(
|
||||
INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID
|
||||
);
|
||||
|
@ -92,6 +98,7 @@ describe('<CorrelationsOverview />', () => {
|
|||
jest.mocked(useShowRelatedAlertsBySameSourceEvent).mockReturnValue({ show: false });
|
||||
jest.mocked(useShowRelatedAlertsBySession).mockReturnValue({ show: false });
|
||||
jest.mocked(useShowRelatedCases).mockReturnValue(false);
|
||||
jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: false, alertSuppressionCount: 0 });
|
||||
|
||||
const { getByTestId, queryByTestId } = render(renderCorrelationsOverview(panelContextValue));
|
||||
expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument();
|
||||
|
@ -111,6 +118,7 @@ describe('<CorrelationsOverview />', () => {
|
|||
.mocked(useShowRelatedAlertsBySession)
|
||||
.mockReturnValue({ show: true, entityId: 'entityId' });
|
||||
jest.mocked(useShowRelatedCases).mockReturnValue(true);
|
||||
jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: true, alertSuppressionCount: 1 });
|
||||
|
||||
(useFetchRelatedAlertsByAncestry as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
|
@ -138,6 +146,7 @@ describe('<CorrelationsOverview />', () => {
|
|||
expect(getByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RELATED_CASES_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(SUPPRESSED_ALERTS_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should hide rows and show error message if show values are false', () => {
|
||||
|
@ -151,12 +160,14 @@ describe('<CorrelationsOverview />', () => {
|
|||
.mocked(useShowRelatedAlertsBySession)
|
||||
.mockReturnValue({ show: false, entityId: 'entityId' });
|
||||
jest.mocked(useShowRelatedCases).mockReturnValue(false);
|
||||
jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: false, alertSuppressionCount: 0 });
|
||||
|
||||
const { getByTestId, queryByTestId } = render(renderCorrelationsOverview(panelContextValue));
|
||||
expect(queryByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(RELATED_CASES_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(SUPPRESSED_ALERTS_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(getByTestId(CORRELATIONS_ERROR_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
@ -165,12 +176,14 @@ describe('<CorrelationsOverview />', () => {
|
|||
jest.mocked(useShowRelatedAlertsBySameSourceEvent).mockReturnValue({ show: true });
|
||||
jest.mocked(useShowRelatedAlertsBySession).mockReturnValue({ show: true });
|
||||
jest.mocked(useShowRelatedCases).mockReturnValue(false);
|
||||
jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: false, alertSuppressionCount: 0 });
|
||||
|
||||
const { queryByTestId } = render(renderCorrelationsOverview(panelContextValue));
|
||||
expect(queryByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(RELATED_CASES_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(queryByTestId(SUPPRESSED_ALERTS_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should navigate to the left section Insights tab when clicking on button', () => {
|
||||
|
|
|
@ -15,6 +15,8 @@ import { useShowRelatedAlertsBySameSourceEvent } from '../../shared/hooks/use_sh
|
|||
import { RelatedAlertsBySameSourceEvent } from './related_alerts_by_same_source_event';
|
||||
import { RelatedAlertsByAncestry } from './related_alerts_by_ancestry';
|
||||
import { useShowRelatedAlertsByAncestry } from '../../shared/hooks/use_show_related_alerts_by_ancestry';
|
||||
import { SuppressedAlerts } from './suppressed_alerts';
|
||||
import { useShowSuppressedAlerts } from '../../shared/hooks/use_show_suppressed_alerts';
|
||||
import { RelatedCases } from './related_cases';
|
||||
import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases';
|
||||
import { INSIGHTS_CORRELATIONS_TEST_ID } from './test_ids';
|
||||
|
@ -68,9 +70,16 @@ export const CorrelationsOverview: React.FC = () => {
|
|||
});
|
||||
const { show: showAlertsBySession, entityId } = useShowRelatedAlertsBySession({ getFieldsData });
|
||||
const showCases = useShowRelatedCases();
|
||||
const { show: showSuppressedAlerts, alertSuppressionCount } = useShowSuppressedAlerts({
|
||||
getFieldsData,
|
||||
});
|
||||
|
||||
const canShowAtLeastOneInsight =
|
||||
showAlertsByAncestry || showSameSourceAlerts || showAlertsBySession || showCases;
|
||||
showAlertsByAncestry ||
|
||||
showSameSourceAlerts ||
|
||||
showAlertsBySession ||
|
||||
showCases ||
|
||||
showSuppressedAlerts;
|
||||
|
||||
return (
|
||||
<ExpandablePanel
|
||||
|
@ -83,16 +92,19 @@ export const CorrelationsOverview: React.FC = () => {
|
|||
>
|
||||
{canShowAtLeastOneInsight ? (
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
{showAlertsByAncestry && documentId && indices && (
|
||||
<RelatedAlertsByAncestry documentId={documentId} indices={indices} scopeId={scopeId} />
|
||||
{showSuppressedAlerts && (
|
||||
<SuppressedAlerts alertSuppressionCount={alertSuppressionCount} />
|
||||
)}
|
||||
{showCases && <RelatedCases eventId={eventId} />}
|
||||
{showSameSourceAlerts && originalEventId && (
|
||||
<RelatedAlertsBySameSourceEvent originalEventId={originalEventId} scopeId={scopeId} />
|
||||
)}
|
||||
{showAlertsBySession && entityId && (
|
||||
<RelatedAlertsBySession entityId={entityId} scopeId={scopeId} />
|
||||
)}
|
||||
{showCases && <RelatedCases eventId={eventId} />}
|
||||
{showAlertsByAncestry && documentId && indices && (
|
||||
<RelatedAlertsByAncestry documentId={documentId} indices={indices} scopeId={scopeId} />
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<div data-test-subj={`${INSIGHTS_CORRELATIONS_TEST_ID}Error`}>{CORRELATIONS_ERROR}</div>
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import {
|
||||
SUMMARY_ROW_ICON_TEST_ID,
|
||||
SUMMARY_ROW_VALUE_TEST_ID,
|
||||
INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID,
|
||||
SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID,
|
||||
} from './test_ids';
|
||||
import { SuppressedAlerts } from './suppressed_alerts';
|
||||
|
||||
const ICON_TEST_ID = SUMMARY_ROW_ICON_TEST_ID(INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID);
|
||||
const VALUE_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID);
|
||||
|
||||
describe('<SuppressedAlerts />', () => {
|
||||
it('should render zero suppressed alert correctly', () => {
|
||||
const { getByTestId } = render(<SuppressedAlerts alertSuppressionCount={0} />);
|
||||
|
||||
expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument();
|
||||
const value = getByTestId(VALUE_TEST_ID);
|
||||
expect(value).toBeInTheDocument();
|
||||
expect(value).toHaveTextContent('0 suppressed alert');
|
||||
expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render single suppressed alert correctly', () => {
|
||||
const { getByTestId } = render(<SuppressedAlerts alertSuppressionCount={1} />);
|
||||
|
||||
expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument();
|
||||
const value = getByTestId(VALUE_TEST_ID);
|
||||
expect(value).toBeInTheDocument();
|
||||
expect(value).toHaveTextContent('1 suppressed alert');
|
||||
expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render multiple suppressed alerts row correctly', () => {
|
||||
const { getByTestId } = render(<SuppressedAlerts alertSuppressionCount={2} />);
|
||||
|
||||
expect(getByTestId(ICON_TEST_ID)).toBeInTheDocument();
|
||||
const value = getByTestId(VALUE_TEST_ID);
|
||||
expect(value).toBeInTheDocument();
|
||||
expect(value).toHaveTextContent('2 suppressed alerts');
|
||||
expect(getByTestId(VALUE_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiBetaBadge } from '@elastic/eui';
|
||||
import {
|
||||
INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID,
|
||||
SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID,
|
||||
} from './test_ids';
|
||||
import { CORRELATIONS_SUPPRESSED_ALERTS } from '../../shared/translations';
|
||||
import { InsightsSummaryRow } from './insights_summary_row';
|
||||
import { SUPPRESSED_ALERTS_COUNT_TECHNICAL_PREVIEW } from '../../../common/components/event_details/insights/translations';
|
||||
import { TECHNICAL_PREVIEW_MESSAGE } from './translations';
|
||||
|
||||
export interface SuppressedAlertsProps {
|
||||
/**
|
||||
* Value of the kibana.alert.suppression.doc_count field
|
||||
*/
|
||||
alertSuppressionCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show related alerts by ancestry in summary row
|
||||
*/
|
||||
export const SuppressedAlerts: React.VFC<SuppressedAlertsProps> = ({ alertSuppressionCount }) => {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<InsightsSummaryRow
|
||||
loading={false}
|
||||
error={false}
|
||||
icon={'layers'}
|
||||
value={alertSuppressionCount}
|
||||
text={CORRELATIONS_SUPPRESSED_ALERTS(alertSuppressionCount)}
|
||||
data-test-subj={INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID}
|
||||
key={`correlation-row-suppressed-alerts`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge
|
||||
label={SUPPRESSED_ALERTS_COUNT_TECHNICAL_PREVIEW}
|
||||
size="s"
|
||||
iconType="beaker"
|
||||
tooltipContent={TECHNICAL_PREVIEW_MESSAGE}
|
||||
tooltipPosition="bottom"
|
||||
data-test-subj={SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
SuppressedAlerts.displayName = 'SuppressedAlerts';
|
|
@ -106,6 +106,10 @@ export const INSIGHTS_THREAT_INTELLIGENCE_CONTAINER_TEST_ID = `${INSIGHTS_THREAT
|
|||
|
||||
export const INSIGHTS_CORRELATIONS_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutInsightsCorrelations';
|
||||
export const INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutInsightsCorrelationsSupressedAlerts';
|
||||
export const SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutSupressedAlertsTechnicalPreview';
|
||||
export const INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutInsightsCorrelationsRelatedCases';
|
||||
export const INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID =
|
||||
|
|
|
@ -299,3 +299,11 @@ export const RESPONSE_TITLE = i18n.translate(
|
|||
export const RESPONSE_EMPTY = i18n.translate('xpack.securitySolution.flyout.response.empty', {
|
||||
defaultMessage: 'There are no response actions defined for this event.',
|
||||
});
|
||||
|
||||
export const TECHNICAL_PREVIEW_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentDetails.technicalPreviewMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ALERT_REASON, ALERT_RISK_SCORE, ALERT_SEVERITY } from '@kbn/rule-data-utils';
|
||||
import {
|
||||
ALERT_REASON,
|
||||
ALERT_RISK_SCORE,
|
||||
ALERT_SEVERITY,
|
||||
ALERT_SUPPRESSION_DOCS_COUNT,
|
||||
} from '@kbn/rule-data-utils';
|
||||
|
||||
/**
|
||||
* Returns mocked data for field (mock this method: x-pack/plugins/security_solution/public/common/hooks/use_get_fields_data.ts)
|
||||
|
@ -24,6 +29,8 @@ export const mockGetFieldsData = (field: string): string[] => {
|
|||
return ['user1'];
|
||||
case ALERT_REASON:
|
||||
return ['reason'];
|
||||
case ALERT_SUPPRESSION_DOCS_COUNT:
|
||||
return ['1'];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
EuiLoadingSpinner,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import type { IconType } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export interface ExpandablePanelPanelProps {
|
||||
|
@ -26,7 +27,7 @@ export interface ExpandablePanelPanelProps {
|
|||
/**
|
||||
* String value of the title to be displayed in the header of panel
|
||||
*/
|
||||
title: string;
|
||||
title: string | React.ReactNode;
|
||||
/**
|
||||
* Callback function to be called when the title is clicked
|
||||
*/
|
||||
|
@ -34,9 +35,9 @@ export interface ExpandablePanelPanelProps {
|
|||
/**
|
||||
* Icon string for displaying the specified icon in the header
|
||||
*/
|
||||
iconType: string;
|
||||
iconType: IconType;
|
||||
/**
|
||||
* Optional content and actions to be displayed on the right side of header
|
||||
* Optional content and actions to be displayed next to header or on the right side of header
|
||||
*/
|
||||
headerContent?: React.ReactNode;
|
||||
};
|
||||
|
@ -106,7 +107,7 @@ export const ExpandablePanel: React.FC<ExpandablePanelPanelProps> = ({
|
|||
|
||||
const headerLeftSection = useMemo(
|
||||
() => (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RenderHookResult } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import type {
|
||||
ShowSuppressedAlertsParams,
|
||||
ShowSuppressedAlertsResult,
|
||||
} from './use_show_suppressed_alerts';
|
||||
import { useShowSuppressedAlerts } from './use_show_suppressed_alerts';
|
||||
|
||||
describe('useShowSuppressedAlerts', () => {
|
||||
let hookResult: RenderHookResult<ShowSuppressedAlertsParams, ShowSuppressedAlertsResult>;
|
||||
|
||||
it('should return false if getFieldsData returns null', () => {
|
||||
const getFieldsData = () => null;
|
||||
hookResult = renderHook(() => useShowSuppressedAlerts({ getFieldsData }));
|
||||
|
||||
expect(hookResult.result.current).toEqual({ show: false, alertSuppressionCount: 0 });
|
||||
});
|
||||
|
||||
it('should return true if getFieldsData has the correct field', () => {
|
||||
const getFieldsData = () => '2';
|
||||
hookResult = renderHook(() => useShowSuppressedAlerts({ getFieldsData }));
|
||||
|
||||
expect(hookResult.result.current).toEqual({ show: true, alertSuppressionCount: 2 });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ALERT_SUPPRESSION_DOCS_COUNT } from '@kbn/rule-data-utils';
|
||||
import type { GetFieldsData } from '../../../common/hooks/use_get_fields_data';
|
||||
|
||||
export interface ShowSuppressedAlertsParams {
|
||||
/**
|
||||
* Retrieves searchHit values for the provided field
|
||||
*/
|
||||
getFieldsData: GetFieldsData;
|
||||
}
|
||||
|
||||
export interface ShowSuppressedAlertsResult {
|
||||
/**
|
||||
* Returns true if the document has kibana.alert.original_event.id field with values
|
||||
*/
|
||||
show: boolean;
|
||||
/**
|
||||
* Number of suppressed alerts
|
||||
*/
|
||||
alertSuppressionCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if document has kibana.alert.suppression.docs_count field with values
|
||||
*/
|
||||
export const useShowSuppressedAlerts = ({
|
||||
getFieldsData,
|
||||
}: ShowSuppressedAlertsParams): ShowSuppressedAlertsResult => {
|
||||
const alertSuppressionField = getFieldsData(ALERT_SUPPRESSION_DOCS_COUNT);
|
||||
const alertSuppressionCount = alertSuppressionField ? parseInt(alertSuppressionField[0], 10) : 0;
|
||||
|
||||
return {
|
||||
show: Boolean(alertSuppressionField),
|
||||
alertSuppressionCount,
|
||||
};
|
||||
};
|
|
@ -19,6 +19,12 @@ export const ERROR_MESSAGE = (message: string) =>
|
|||
defaultMessage: 'There was an error displaying {message}',
|
||||
});
|
||||
|
||||
export const CORRELATIONS_SUPPRESSED_ALERTS = (count: number) =>
|
||||
i18n.translate('xpack.securitySolution.flyout.documentDetails.correlations.suppressedAlerts', {
|
||||
defaultMessage: 'suppressed {count, plural, =1 {alert} other {alerts}}',
|
||||
values: { count },
|
||||
});
|
||||
|
||||
export const CORRELATIONS_ANCESTRY_ALERTS = (count: number) =>
|
||||
i18n.translate('xpack.securitySolution.flyout.documentDetails.correlations.ancestryAlerts', {
|
||||
defaultMessage: '{count, plural, one {alert} other {alerts}} related by ancestry',
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
import { createRule } from '../../../../tasks/api_calls/rules';
|
||||
import { getNewRule } from '../../../../objects/rule';
|
||||
import {
|
||||
CORRELATIONS_ANCESTRY_SECTION_INVESTIGATE_IN_TIMELINE_BUTTON,
|
||||
CORRELATIONS_ANCESTRY_SECTION_TABLE,
|
||||
CORRELATIONS_ANCESTRY_SECTION_TITLE,
|
||||
CORRELATIONS_CASES_SECTION_TABLE,
|
||||
CORRELATIONS_CASES_SECTION_TITLE,
|
||||
CORRELATIONS_SESSION_SECTION_INVESTIGATE_IN_TIMELINE_BUTTON,
|
||||
CORRELATIONS_SESSION_SECTION_TABLE,
|
||||
CORRELATIONS_SESSION_SECTION_TITLE,
|
||||
DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON,
|
||||
|
@ -73,6 +75,7 @@ describe(
|
|||
.should('be.visible')
|
||||
.and('contain.text', '1 alert related by ancestry');
|
||||
cy.get(CORRELATIONS_ANCESTRY_SECTION_TABLE).should('be.visible');
|
||||
cy.get(CORRELATIONS_ANCESTRY_SECTION_INVESTIGATE_IN_TIMELINE_BUTTON).should('be.visible');
|
||||
|
||||
// TODO get proper data to test this section
|
||||
// cy.get(CORRELATIONS_SOURCE_SECTION).scrollIntoView();
|
||||
|
@ -80,18 +83,27 @@ describe(
|
|||
// .should('be.visible')
|
||||
// .and('contain.text', '0 alerts related by source event');
|
||||
// cy.get(CORRELATIONS_SOURCE_SECTION_TABLE).should('be.visible');
|
||||
// cy.get(CORRELATIONS_SESSION_SECTION_INVESTIGATE_IN_TIMELINE_BUTTON).should('be.visible');
|
||||
|
||||
cy.get(CORRELATIONS_SESSION_SECTION_TITLE).scrollIntoView();
|
||||
cy.get(CORRELATIONS_SESSION_SECTION_TITLE)
|
||||
.should('be.visible')
|
||||
.and('contain.text', '1 alert related by session');
|
||||
cy.get(CORRELATIONS_SESSION_SECTION_TABLE).should('be.visible');
|
||||
cy.get(CORRELATIONS_SESSION_SECTION_INVESTIGATE_IN_TIMELINE_BUTTON).should('be.visible');
|
||||
|
||||
cy.get(CORRELATIONS_CASES_SECTION_TITLE).scrollIntoView();
|
||||
cy.get(CORRELATIONS_CASES_SECTION_TITLE)
|
||||
.should('be.visible')
|
||||
.and('contain.text', '1 related case');
|
||||
cy.get(CORRELATIONS_CASES_SECTION_TABLE).should('be.visible');
|
||||
|
||||
// TODO get proper data to test suppressed alerts
|
||||
// cy.get(CORRELATIONS_SUPPRESSED_ALERTS_TITLE).scrollIntoView();
|
||||
// cy.get(CORRELATIONS_SUPPRESSED_ALERTS_TITLE)
|
||||
// .should('be.visible')
|
||||
// .and('contain.text', '1 suppressed alert');
|
||||
// cy.get(CORRELATIONS_SUPPRESSED_ALERTS_INVESTIGATE_IN_TIMELINE_BUTTON).should('be.visible');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -293,7 +293,9 @@ describe(
|
|||
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT)
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
// TODO the order in which these appear is not deterministic currently, hence this can cause flakiness
|
||||
// cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_SUPPRESSED_ALERTS)
|
||||
// .should('be.visible')
|
||||
// .and('have.text', '1 suppressed alert'); // TODO populate rule with alert suppression
|
||||
cy.get(
|
||||
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_ANCESTRY
|
||||
)
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID,
|
||||
CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID,
|
||||
CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID,
|
||||
CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID,
|
||||
} from '@kbn/security-solution-plugin/public/flyout/left/components/test_ids';
|
||||
import { EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/shared/components/test_ids';
|
||||
import { getDataTestSubjectSelector } from '../../helpers/common';
|
||||
|
@ -27,6 +28,11 @@ export const CORRELATIONS_ANCESTRY_SECTION_TABLE = getDataTestSubjectSelector(
|
|||
`${CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID}Table`
|
||||
);
|
||||
|
||||
export const CORRELATIONS_ANCESTRY_SECTION_INVESTIGATE_IN_TIMELINE_BUTTON =
|
||||
getDataTestSubjectSelector(
|
||||
`${CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID}InvestigateInTimeline`
|
||||
);
|
||||
|
||||
export const CORRELATIONS_SOURCE_SECTION_TITLE = getDataTestSubjectSelector(
|
||||
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID)
|
||||
);
|
||||
|
@ -35,6 +41,11 @@ export const CORRELATIONS_SOURCE_SECTION_TABLE = getDataTestSubjectSelector(
|
|||
`${CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID}Table`
|
||||
);
|
||||
|
||||
export const CORRELATIONS_SOURCE_SECTION_INVESTIGATE_IN_TIMELINE_BUTTON =
|
||||
getDataTestSubjectSelector(
|
||||
`${CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID}InvestigateInTimeline`
|
||||
);
|
||||
|
||||
export const CORRELATIONS_SESSION_SECTION_TITLE = getDataTestSubjectSelector(
|
||||
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID)
|
||||
);
|
||||
|
@ -43,6 +54,11 @@ export const CORRELATIONS_SESSION_SECTION_TABLE = getDataTestSubjectSelector(
|
|||
`${CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID}Table`
|
||||
);
|
||||
|
||||
export const CORRELATIONS_SESSION_SECTION_INVESTIGATE_IN_TIMELINE_BUTTON =
|
||||
getDataTestSubjectSelector(
|
||||
`${CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID}InvestigateInTimeline`
|
||||
);
|
||||
|
||||
export const CORRELATIONS_CASES_SECTION_TITLE = getDataTestSubjectSelector(
|
||||
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID)
|
||||
);
|
||||
|
@ -50,3 +66,12 @@ export const CORRELATIONS_CASES_SECTION_TITLE = getDataTestSubjectSelector(
|
|||
export const CORRELATIONS_CASES_SECTION_TABLE = getDataTestSubjectSelector(
|
||||
`${CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID}Table`
|
||||
);
|
||||
|
||||
export const CORRELATIONS_SUPPRESSED_ALERTS_TITLE = getDataTestSubjectSelector(
|
||||
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID)
|
||||
);
|
||||
|
||||
export const CORRELATIONS_SUPPRESSED_ALERTS_INVESTIGATE_IN_TIMELINE_BUTTON =
|
||||
getDataTestSubjectSelector(
|
||||
`${CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID}InvestigateInTimeline`
|
||||
);
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID,
|
||||
INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID,
|
||||
INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID,
|
||||
INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID,
|
||||
INSIGHTS_ENTITIES_TEST_ID,
|
||||
REASON_DETAILS_PREVIEW_BUTTON_TEST_ID,
|
||||
ANALYZER_PREVIEW_TEST_ID,
|
||||
|
@ -122,6 +123,10 @@ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER =
|
|||
);
|
||||
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT =
|
||||
getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(INSIGHTS_CORRELATIONS_TEST_ID));
|
||||
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_SUPPRESSED_ALERTS =
|
||||
getDataTestSubjectSelector(
|
||||
SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID)
|
||||
);
|
||||
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES_RELATED_ALERTS_BY_ANCESTRY =
|
||||
getDataTestSubjectSelector(
|
||||
SUMMARY_ROW_VALUE_TEST_ID(INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue