Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CTI] Adds Threat Intel Tab to Alert Summary Flyout #97185

Merged
merged 12 commits into from
Apr 19, 2021
14 changes: 13 additions & 1 deletion x-pack/plugins/security_solution/common/cti/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ export const INDICATOR_MATCHED_TYPE = `${INDICATOR_DESTINATION_PATH}.${MATCHED_T
export const EVENT_DATASET = 'event.dataset';
export const EVENT_REFERENCE = 'event.reference';
export const PROVIDER = 'provider';
export const FIRSTSEEN = 'first_seen';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
export const FIRSTSEEN = 'first_seen';
export const FIRST_SEEN = 'first_seen';

Not worth waiting for another build, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_ seemed to indicate nesting level - leaving as is now, I can update in a later commit


export const INDICATOR_DATASET = `${INDICATOR_DESTINATION_PATH}.${EVENT_DATASET}`;
export const INDICATOR_REFERENCE = `${INDICATOR_DESTINATION_PATH}.${EVENT_REFERENCE}`;
export const INDICATOR_EVENT_URL = `${INDICATOR_DESTINATION_PATH}.event.url`;
export const INDICATOR_FIRSTSEEN = `${INDICATOR_DESTINATION_PATH}.${FIRSTSEEN}`;
export const INDICATOR_LASTSEEN = `${INDICATOR_DESTINATION_PATH}.last_seen`;
export const INDICATOR_PROVIDER = `${INDICATOR_DESTINATION_PATH}.${PROVIDER}`;
export const INDICATOR_REFERENCE = `${INDICATOR_DESTINATION_PATH}.${EVENT_REFERENCE}`;

export const CTI_ROW_RENDERER_FIELDS = [
INDICATOR_MATCHED_ATOMIC,
Expand All @@ -32,3 +36,11 @@ export const CTI_ROW_RENDERER_FIELDS = [
INDICATOR_REFERENCE,
INDICATOR_PROVIDER,
];

export const SORTED_THREAT_SUMMARY_FIELDS = [
INDICATOR_MATCHED_FIELD,
INDICATOR_MATCHED_TYPE,
INDICATOR_PROVIDER,
INDICATOR_FIRSTSEEN,
INDICATOR_LASTSEEN,
];
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import {
EuiDescriptionList,
EuiDescriptionListDescription,
EuiDescriptionListTitle,
EuiSpacer,
} from '@elastic/eui';
import { get, getOr } from 'lodash/fp';
import React, { useMemo } from 'react';
import styled from 'styled-components';

import * as i18n from './translations';
import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field';
import { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
import { BrowserFields } from '../../../../common/search_strategy/index_fields';
Expand All @@ -33,7 +36,6 @@ import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../../../networ
import { SummaryView } from './summary_view';
import { AlertSummaryRow, getSummaryColumns, SummaryRow } from './helpers';
import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async';
import * as i18n from './translations';
import { LineClamp } from '../line_clamp';

const StyledEuiDescriptionList = styled(EuiDescriptionList)`
Expand Down Expand Up @@ -166,7 +168,8 @@ const AlertSummaryViewComponent: React.FC<{
data: TimelineEventsDetailsItem[];
eventId: string;
timelineId: string;
}> = ({ browserFields, data, eventId, timelineId }) => {
title?: string;
}> = ({ browserFields, data, eventId, timelineId, title }) => {
const summaryRows = useMemo(() => getSummaryRows({ browserFields, data, eventId, timelineId }), [
browserFields,
data,
Expand All @@ -184,7 +187,8 @@ const AlertSummaryViewComponent: React.FC<{

return (
<>
<SummaryView summaryColumns={summaryColumns} summaryRows={summaryRows} />
<EuiSpacer size="l" />
<SummaryView summaryColumns={summaryColumns} summaryRows={summaryRows} title={title} />
{maybeRule?.note && (
<StyledEuiDescriptionList data-test-subj={`summary-view-guide`} compressed>
<EuiDescriptionListTitle>{i18n.INVESTIGATION_GUIDE}</EuiDescriptionListTitle>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 { ThemeProvider } from 'styled-components';

import { useMountAppended } from '../../utils/use_mount_appended';
import { getMockTheme } from '../../lib/kibana/kibana_react.mock';
import { EmptyThreatDetailsView } from './empty_threat_details_view';

jest.mock('../../lib/kibana');

describe('EmptyThreatDetailsView', () => {
const mount = useMountAppended();
const mockTheme = getMockTheme({
eui: {
euiBreakpoints: {
l: '1200px',
},
paddingSizes: {
m: '8px',
xl: '32px',
},
},
});

beforeEach(() => {
jest.clearAllMocks();
});

test('renders correct items', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<EmptyThreatDetailsView />
</ThemeProvider>
);
expect(wrapper.find('[data-test-subj="empty-threat-details-view"]').exists()).toEqual(true);
});

test('renders link to docs', () => {
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<EmptyThreatDetailsView />
</ThemeProvider>
);
expect(wrapper.find('a').exists()).toEqual(true);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 { EuiLink, EuiSpacer, EuiTitle } from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';
import * as i18n from './translations';
import { useKibana } from '../../lib/kibana';

const EmptyThreatDetailsViewContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`;

const Span = styled.span`
color: ${({ theme }) => theme.eui.euiColorDarkShade};
line-height: 1.8em;
text-align: center;
padding: ${({ theme }) => `${theme.eui.paddingSizes.m} ${theme.eui.paddingSizes.xl}`};
`;

const EmptyThreatDetailsViewComponent: React.FC<{}> = () => {
const threatIntelDocsUrl = `${
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

useKibana().services.docLinks.links.filebeat.base
}/filebeat-module-threatintel.html`;

return (
<EmptyThreatDetailsViewContainer data-test-subj="empty-threat-details-view">
<EuiSpacer size="xxl" />
<EuiTitle size="m">
<h2>{i18n.NO_ENRICHMENT_FOUND}</h2>
</EuiTitle>
<Span>
{i18n.IF_CTI_NOT_ENABLED}
<EuiLink href={threatIntelDocsUrl} target="_blank">
{i18n.CHECK_DOCS}
</EuiLink>
</Span>
</EmptyThreatDetailsViewContainer>
);
};

EmptyThreatDetailsViewComponent.displayName = 'EmptyThreatDetailsView';

export const EmptyThreatDetailsView = React.memo(EmptyThreatDetailsViewComponent);
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import '../../mock/match_media';
import '../../mock/react_beautiful_dnd';
import { mockDetailItemData, mockDetailItemDataId, TestProviders } from '../../mock';

import { EventDetails, EventsViewType, EventView, ThreatView } from './event_details';
import { EventDetails, EventsViewType } from './event_details';
import { mockBrowserFields } from '../../containers/source/mock';
import { useMountAppended } from '../../utils/use_mount_appended';
import { mockAlertDetailsData } from './__mocks__';
Expand All @@ -32,8 +32,7 @@ describe('EventDetails', () => {
onThreatViewSelected: jest.fn(),
timelineTabType: TimelineTabs.query,
timelineId: 'test',
eventView: EventsViewType.summaryView as EventView,
threatView: EventsViewType.threatSummaryView as ThreatView,
eventView: EventsViewType.summaryView,
};

const alertsProps = {
Expand Down Expand Up @@ -78,13 +77,14 @@ describe('EventDetails', () => {
});

describe('alerts tabs', () => {
['Summary', 'Table', 'JSON View'].forEach((tab) => {
['Summary', 'Threat Intel', 'Table', 'JSON View'].forEach((tab) => {
test(`it renders the ${tab} tab`, () => {
const expectedCopy = tab === 'Threat Intel' ? `${tab} (1)` : tab;
expect(
alertsWrapper
.find('[data-test-subj="eventDetails"]')
.find('[role="tablist"]')
.containsMatchingElement(<span>{tab}</span>)
.containsMatchingElement(<span>{expectedCopy}</span>)
).toBeTruthy();
});
});
Expand All @@ -99,27 +99,4 @@ describe('EventDetails', () => {
).toEqual('Summary');
});
});

describe('threat tabs', () => {
['Threat Summary', 'Threat Details'].forEach((tab) => {
test(`it renders the ${tab} tab`, () => {
expect(
alertsWrapper
.find('[data-test-subj="threatDetails"]')
.find('[role="tablist"]')
.containsMatchingElement(<span>{tab}</span>)
).toBeTruthy();
});
});

test('the Summary tab is selected by default', () => {
expect(
alertsWrapper
.find('[data-test-subj="threatDetails"]')
.find('.euiTab-isSelected')
.first()
.text()
).toEqual('Threat Summary');
});
});
});
Loading