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

[7.x] [CTI] updates Alert Summary UI (#107081) #107605

Merged
merged 1 commit into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import { getEmptyValue } from '../empty_value';
import { ActionCell } from './table/action_cell';
import { FieldValueCell } from './table/field_value_cell';
import { TimelineEventsDetailsItem } from '../../../../common';
import { EventFieldsData } from './types';

export const Indent = styled.div`
padding: 0 8px;
Expand Down Expand Up @@ -95,23 +94,19 @@ const getDescription = ({
return <StyledEmptyComponent>{getEmptyValue()}</StyledEmptyComponent>;
}

const eventFieldsData = {
...data,
...(fieldFromBrowserField ? fieldFromBrowserField : {}),
} as EventFieldsData;
return (
<>
<FieldValueCell
contextId={timelineId}
data={eventFieldsData}
data={data}
eventId={eventId}
fieldFromBrowserField={fieldFromBrowserField}
linkValue={linkValue}
values={values}
/>
<ActionCell
contextId={timelineId}
data={eventFieldsData}
data={data}
eventId={eventId}
fieldFromBrowserField={fieldFromBrowserField}
linkValue={linkValue}
Expand Down Expand Up @@ -178,7 +173,13 @@ const getSummaryRows = ({
const browserField = get([category, 'fields', fieldName], browserFields);
const description = {
...initialDescription,
data: { ...field, ...(item.overrideField ? { field: item.overrideField } : {}) },
data: {
field: field.field,
format: browserField?.format ?? '',
type: browserField?.type ?? '',
isObjectArray: field.isObjectArray,
...(item.overrideField ? { field: item.overrideField } : {}),
},
values: field.values,
linkValue: linkValue ?? undefined,
fieldFromBrowserField: browserField,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { BrowserFields } from '../../containers/source';
import { OnUpdateColumns } from '../../../timelines/components/timeline/events';
import * as i18n from './translations';
import { EventFieldsData } from './types';
import { ColumnHeaderOptions } from '../../../../common';
import { BrowserField, ColumnHeaderOptions } from '../../../../common';
import { FieldValueCell } from './table/field_value_cell';
import { FieldNameCell } from './table/field_name_cell';
import { ActionCell } from './table/action_cell';
Expand All @@ -36,7 +36,7 @@ const HoverActionsContainer = styled(EuiPanel)`
HoverActionsContainer.displayName = 'HoverActionsContainer';

export const getFieldFromBrowserField = memoizeOne(
(keys: string[], browserFields: BrowserFields): BrowserFields => get(browserFields, keys),
(keys: string[], browserFields: BrowserFields): BrowserField => get(browserFields, keys),
(newArgs, lastArgs) => newArgs[0].join() === lastArgs[0].join()
);
export const getColumns = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,6 @@ const ThreatDetailsViewComponent: React.FC<{
<h5>{i18n.INVESTIGATION_TOOLTIP_TITLE}</h5>
</EuiTitle>
<EuiSpacer size="xs" />
{/* TODO: Date form */}
<EuiText size="xs">
<FormattedMessage
id="xpack.securitySolution.alertDetails.threatDetails.investigationSubtitle"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,34 @@ import { ThreatSummaryView } from './threat_summary_view';
import { TestProviders } from '../../../mock';
import { useMountAppended } from '../../../utils/use_mount_appended';
import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock';
import { mockAlertDetailsData } from '../__mocks__';
import { TimelineEventsDetailsItem } from '../../../../../../timelines/common';
import { mockBrowserFields } from '../../../containers/source/mock';

jest.mock('../../../../timelines/components/timeline/body/renderers/formatted_field');
jest.mock('../table/action_cell');
jest.mock('../table/field_name_cell');

describe('ThreatSummaryView', () => {
const mount = useMountAppended();
const eventId = '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31';
const timelineId = 'detections-page';

beforeEach(() => {
jest.clearAllMocks();
});
const data = mockAlertDetailsData as TimelineEventsDetailsItem[];
const browserFields = mockBrowserFields;

it('renders a row for each enrichment', () => {
const enrichments = [
buildEventEnrichmentMock(),
buildEventEnrichmentMock({ 'matched.id': ['test.id'], 'matched.field': ['test.field'] }),
buildEventEnrichmentMock({ 'matched.id': ['other.id'], 'matched.field': ['other.field'] }),
];
const wrapper = mount(
<TestProviders>
<ThreatSummaryView enrichments={enrichments} eventId={eventId} timelineId={timelineId} />
<ThreatSummaryView
data={data}
browserFields={browserFields}
enrichments={enrichments}
eventId={eventId}
timelineId={timelineId}
/>
</TestProviders>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,33 @@
*/

import styled from 'styled-components';
import React from 'react';
import { get } from 'lodash/fp';
import React, { Fragment } from 'react';
import { EuiBasicTableColumn, EuiText, EuiTitle } from '@elastic/eui';

import * as i18n from './translations';
import { Indent, StyledEuiInMemoryTable } from '../summary_view';
import { FormattedFieldValue } from '../../../../timelines/components/timeline/body/renderers/formatted_field';
import { CtiEnrichment } from '../../../../../common/search_strategy/security_solution/cti';
import { getEnrichmentIdentifiers } from './helpers';
import { EnrichmentIcon } from './enrichment_icon';
import { FieldsData } from '../types';
import { ActionCell } from '../table/action_cell';
import { BrowserField, BrowserFields, TimelineEventsDetailsItem } from '../../../../../common';
import { FieldValueCell } from '../table/field_value_cell';

export interface ThreatSummaryItem {
title: {
title: string | undefined;
type: string | undefined;
};
description: {
timelineId: string;
browserField: BrowserField;
data: FieldsData | undefined;
eventId: string;
fieldName: string | undefined;
index: number;
value: string | undefined;
provider: string | undefined;
timelineId: string;
value: string | undefined;
};
}

Expand All @@ -48,24 +53,25 @@ const EnrichmentTitle: React.FC<ThreatSummaryItem['title']> = ({ title, type })
);

const EnrichmentDescription: React.FC<ThreatSummaryItem['description']> = ({
timelineId,
browserField,
data,
eventId,
fieldName,
index,
value,
provider,
timelineId,
value,
}) => {
const key = `alert-details-value-formatted-field-value-${timelineId}-${eventId}-${fieldName}-${value}-${index}-${provider}`;
if (!data || !value) return null;
const key = `alert-details-value-formatted-field-value-${timelineId}-${eventId}-${data.field}-${value}-${index}-${provider}`;
return (
<>
<Fragment key={key}>
<RightMargin>
<FormattedFieldValue
key={key}
contextId={key}
eventId={eventId}
isDraggable={false}
fieldName={fieldName || 'unknown'}
value={value}
<FieldValueCell
contextId={timelineId}
data={data}
eventId={key}
fieldFromBrowserField={browserField}
values={[value]}
/>
</RightMargin>
{provider && (
Expand All @@ -82,17 +88,39 @@ const EnrichmentDescription: React.FC<ThreatSummaryItem['description']> = ({
</RightMargin>
</>
)}
</>
{value && (
<ActionCell
data={data}
contextId={timelineId}
eventId={key}
fieldFromBrowserField={browserField}
timelineId={timelineId}
values={[value]}
/>
)}
</Fragment>
);
};

const buildThreatSummaryItems = (
browserFields: BrowserFields,
data: TimelineEventsDetailsItem[],
enrichments: CtiEnrichment[],
timelineId: string,
eventId: string
) => {
return enrichments.map((enrichment, index) => {
const { field, type, value, provider } = getEnrichmentIdentifiers(enrichment);
const eventData = data.find((item) => item.field === field);
const category = eventData?.category ?? '';
const browserField = get([category, 'fields', field ?? ''], browserFields);

const fieldsData = {
field,
format: browserField?.format ?? '',
type: browserField?.type ?? '',
isObjectArray: eventData?.isObjectArray,
};

return {
title: {
Expand All @@ -101,11 +129,12 @@ const buildThreatSummaryItems = (
},
description: {
eventId,
fieldName: field,
index,
provider,
timelineId,
value,
data: fieldsData,
browserField,
},
};
});
Expand All @@ -116,7 +145,7 @@ const columns: Array<EuiBasicTableColumn<ThreatSummaryItem>> = [
field: 'title',
truncateText: false,
render: EnrichmentTitle,
width: '160px',
width: '220px',
name: '',
},
{
Expand All @@ -131,13 +160,15 @@ const ThreatSummaryViewComponent: React.FC<{
enrichments: CtiEnrichment[];
timelineId: string;
eventId: string;
}> = ({ enrichments, timelineId, eventId }) => (
data: TimelineEventsDetailsItem[];
browserFields: BrowserFields;
}> = ({ enrichments, timelineId, eventId, data, browserFields }) => (
<Indent>
<StyledEuiInMemoryTable
columns={columns}
compressed
data-test-subj="threat-summary-view"
items={buildThreatSummaryItems(enrichments, timelineId, eventId)}
items={buildThreatSummaryItems(browserFields, data, enrichments, timelineId, eventId)}
/>
</Indent>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ const EventDetailsComponent: React.FC<Props> = ({
/>
{enrichmentCount > 0 && (
<ThreatSummaryView
browserFields={browserFields}
data={data}
eventId={id}
timelineId={timelineId}
enrichments={allEnrichments}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { get, getOr, isEmpty, uniqBy } from 'lodash/fp';

import styled from 'styled-components';
import React from 'react';
import { EuiBasicTableColumn, EuiTitle } from '@elastic/eui';
import {
Expand All @@ -21,9 +22,10 @@ import {
DEFAULT_DATE_COLUMN_MIN_WIDTH,
DEFAULT_COLUMN_MIN_WIDTH,
} from '../../../timelines/components/timeline/body/constants';
import { FieldsData } from './types';

import * as i18n from './translations';
import { ColumnHeaderOptions, TimelineEventsDetailsItem } from '../../../../common';
import { ColumnHeaderOptions } from '../../../../common';

/**
* Defines the behavior of the search input that appears above the table of data
Expand Down Expand Up @@ -55,9 +57,9 @@ export interface Item {
export interface AlertSummaryRow {
title: string;
description: {
data: TimelineEventsDetailsItem;
data: FieldsData;
eventId: string;
fieldFromBrowserField?: Readonly<Record<string, Partial<BrowserField>>>;
fieldFromBrowserField?: BrowserField;
linkValue: string | undefined;
timelineId: string;
values: string[] | null | undefined;
Expand Down Expand Up @@ -196,9 +198,13 @@ export const onEventDetailsTabKeyPressed = ({
}
};

const StyledH5 = styled.h5`
line-height: 1.7rem;
`;

const getTitle = (title: string) => (
<EuiTitle size="xxxs">
<h5>{title}</h5>
<StyledH5>{title}</StyledH5>
</EuiTitle>
);
getTitle.displayName = 'getTitle';
Expand All @@ -213,7 +219,7 @@ export const getSummaryColumns = (
field: 'title',
truncateText: false,
render: getTitle,
width: '33%',
width: '220px',
name: '',
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
import React, { useCallback, useState, useRef } from 'react';
import { HoverActions } from '../../hover_actions';
import { useActionCellDataProvider } from './use_action_cell_data_provider';
import { EventFieldsData } from '../types';
import { EventFieldsData, FieldsData } from '../types';
import { useGetTimelineId } from '../../drag_and_drop/use_get_timeline_id_from_dom';
import { ColumnHeaderOptions } from '../../../../../common/types/timeline';
import { BrowserField } from '../../../containers/source';

interface Props {
contextId: string;
data: EventFieldsData;
data: FieldsData | EventFieldsData;
disabled?: boolean;
eventId: string;
fieldFromBrowserField?: Readonly<Record<string, Partial<BrowserField>>>;
fieldFromBrowserField?: BrowserField;
getLinkValue?: (field: string) => string | null;
linkValue?: string | null | undefined;
onFilterAdded?: () => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { getFieldTypeName } from './get_field_type_name';
export interface FieldNameCellProps {
data: EventFieldsData;
field: string;
fieldFromBrowserField: Readonly<Record<string, Partial<BrowserField>>>;
fieldFromBrowserField: BrowserField;
fieldMapping?: IndexPatternField;
scripted?: boolean;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import { BrowserField } from '../../../containers/source';
import { OverflowField } from '../../tables/helpers';
import { FormattedFieldValue } from '../../../../timelines/components/timeline/body/renderers/formatted_field';
import { MESSAGE_FIELD_NAME } from '../../../../timelines/components/timeline/body/renderers/constants';
import { EventFieldsData } from '../types';
import { EventFieldsData, FieldsData } from '../types';

export interface FieldValueCellProps {
contextId: string;
data: EventFieldsData;
data: EventFieldsData | FieldsData;
eventId: string;
fieldFromBrowserField?: Readonly<Record<string, Partial<BrowserField>>>;
fieldFromBrowserField?: BrowserField;
getLinkValue?: (field: string) => string | null;
linkValue?: string | null | undefined;
values: string[] | null | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface UseActionCellDataProvider {
eventId?: string;
field: string;
fieldFormat?: string;
fieldFromBrowserField?: Readonly<Record<string, Partial<BrowserField>>>;
fieldFromBrowserField?: BrowserField;
fieldType?: string;
isObjectArray?: boolean;
linkValue?: string | null;
Expand Down
Loading