Skip to content
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 @@ -8,13 +8,19 @@
import { render } from '@testing-library/react';
import { TestProviders } from '../../../../common/mock';
import React from 'react';
import { EnrichmentAccordion } from './threat_details_view_enrichment_accordion';
import {
ENRICHMENT_ACCORDION_LINK_TEST_ID,
EnrichmentAccordion,
THREAT_DETAILS_ROW_FIELD_TEST_ID,
THREAT_DETAILS_ROW_LINK_VALUE_TEST_ID,
THREAT_DETAILS_ROW_STRING_VALUE_TEST_ID,
} from './threat_details_view_enrichment_accordion';
import { indicatorWithNestedObjects } from '../mocks/indicator_with_nested_objects';
import type { CtiEnrichment } from '../../../../../common/search_strategy';

describe('EnrichmentAccordion', () => {
it('should render', () => {
render(
it('should render the top level accordion', () => {
const { getByTestId } = render(
<TestProviders>
<EnrichmentAccordion
enrichment={indicatorWithNestedObjects as unknown as CtiEnrichment}
Expand All @@ -23,6 +29,33 @@ describe('EnrichmentAccordion', () => {
</TestProviders>
);

expect(true).toBeTruthy();
expect(getByTestId(ENRICHMENT_ACCORDION_LINK_TEST_ID)).toBeInTheDocument();
});

it('should render rows with EuiLink', () => {
const { getAllByTestId, getAllByText, getByText } = render(
<TestProviders>
<EnrichmentAccordion
enrichment={indicatorWithNestedObjects as unknown as CtiEnrichment}
index={0}
/>
</TestProviders>
);

expect(getAllByTestId(THREAT_DETAILS_ROW_FIELD_TEST_ID).length).toBeGreaterThan(0);
expect(getAllByTestId(THREAT_DETAILS_ROW_STRING_VALUE_TEST_ID).length).toBeGreaterThan(0);
expect(getAllByTestId(THREAT_DETAILS_ROW_LINK_VALUE_TEST_ID).length).toEqual(1);

expect(getByText('@timestamp')).toBeInTheDocument();
expect(getAllByText('2024-02-24T17:32:37.813Z').length).toBeGreaterThan(0);

expect(getByText('agent.name')).toBeInTheDocument();
expect(getByText('win-10')).toBeInTheDocument();

expect(getByText('data_stream.namespace')).toBeInTheDocument();
expect(getByText('default')).toBeInTheDocument();

expect(getByText('indicator.reference')).toBeInTheDocument();
expect(getByText('indicatorReferenceValue')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
EuiAccordion,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiInMemoryTable,
EuiLink,
EuiTitle,
EuiToolTip,
useEuiTheme,
Expand All @@ -24,8 +24,8 @@ import type { CtiEnrichment } from '../../../../../common/search_strategy';
import { QUERY_ID } from '../../shared/hooks/use_investigation_enrichment';
import { InspectButton } from '../../../../common/components/inspect';
import {
getEnrichmentIdentifiers,
buildThreatDetailsItems,
getEnrichmentIdentifiers,
isInvestigationTimeEnrichment,
} from '../../shared/utils/threat_intelligence';
import { EnrichmentButtonContent } from './threat_details_view_enrichment_button_content';
Expand All @@ -35,6 +35,11 @@ const StyledH5 = styled.h5`
line-height: 1.7rem;
`;

export const ENRICHMENT_ACCORDION_LINK_TEST_ID = 'enrichementAccordion';
export const THREAT_DETAILS_ROW_FIELD_TEST_ID = 'threatDetailsRowField';
export const THREAT_DETAILS_ROW_LINK_VALUE_TEST_ID = 'threatDetailsRowLinkValue';
export const THREAT_DETAILS_ROW_STRING_VALUE_TEST_ID = 'threatDetailsRowStringValue';

const INVESTIGATION_QUERY_TITLE = i18n.translate(
'xpack.securitySolution.flyout.threatIntelligence.investigationTimeQueryTitle',
{
Expand All @@ -61,7 +66,7 @@ export interface ThreatDetailsRow {
/**
* Value of the enrichment
*/
value: string;
value: string[];
};
}

Expand All @@ -70,7 +75,7 @@ const columns: Array<EuiBasicTableColumn<ThreatDetailsRow>> = [
field: 'title',
truncateText: false,
render: (title: string) => (
<EuiTitle size="xxxs">
<EuiTitle data-test-subj={THREAT_DETAILS_ROW_FIELD_TEST_ID} size="xxxs">
<StyledH5>{title}</StyledH5>
</EuiTitle>
),
Expand All @@ -82,13 +87,27 @@ const columns: Array<EuiBasicTableColumn<ThreatDetailsRow>> = [
truncateText: false,
render: (description: ThreatDetailsRow['description']) => {
const { fieldName, value } = description;
const tooltipChild = fieldName.match(REFERENCE) ? (
<EuiLink href={value} target="_blank">
{value}
</EuiLink>

const renderedValue = fieldName.match(REFERENCE) ? (
<EuiFlexGroup direction="column" gutterSize="none">
{value.map((val, key) => (
<EuiFlexItem data-test-subj={THREAT_DETAILS_ROW_LINK_VALUE_TEST_ID} key={key}>
<EuiLink href={val} target="_blank">
{val}
</EuiLink>
</EuiFlexItem>
))}
</EuiFlexGroup>
) : (
<span>{value}</span>
<EuiFlexGroup direction="column" gutterSize="none">
{value.map((val, key) => (
<EuiFlexItem data-test-subj={THREAT_DETAILS_ROW_STRING_VALUE_TEST_ID} key={key}>
<span>{val}</span>
</EuiFlexItem>
))}
</EuiFlexGroup>
);

return (
<EuiToolTip
data-test-subj="message-tool-tip"
Expand All @@ -100,7 +119,7 @@ const columns: Array<EuiBasicTableColumn<ThreatDetailsRow>> = [
</EuiFlexGroup>
}
>
{tooltipChild}
{renderedValue}
</EuiToolTip>
);
},
Expand Down Expand Up @@ -137,6 +156,7 @@ export const EnrichmentAccordion = memo(({ enrichment, index }: EnrichmentAccord

return (
<EuiAccordion
data-test-subj={ENRICHMENT_ACCORDION_LINK_TEST_ID}
id={accordionId}
key={accordionId}
initialIsOpen={true}
Expand All @@ -156,6 +176,7 @@ export const EnrichmentAccordion = memo(({ enrichment, index }: EnrichmentAccord
height: ${euiTheme.size.xl};
margin-bottom: ${euiTheme.size.s};
padding-left: ${euiTheme.size.s};
}
`}
>
<EuiInMemoryTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,5 @@ export const indicatorWithNestedObjects = {
'agent.ephemeral_id': ['0532c813-1434-4c76-800b-6abdf7eaf62c'],
'agent.version': ['8.10.4'],
'event.dataset': ['ti_recordedfuture.threat'],
'indicator.reference': ['indicatorReferenceValue'],
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants';
import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock';
import {
buildThreatDetailsItems,
filterDuplicateEnrichments,
getEnrichmentFields,
parseExistingEnrichments,
getEnrichmentIdentifiers,
buildThreatDetailsItems,
parseExistingEnrichments,
} from './threat_intelligence';

describe('parseExistingEnrichments', () => {
Expand Down Expand Up @@ -510,28 +510,28 @@ describe('buildThreatDetailsItems', () => {
{
description: {
fieldName: 'feed.name',
value: 'feed name',
value: ['feed name'],
},
title: 'feed.name',
},
{
description: {
fieldName: 'matched.atomic',
value: 'matched atomic',
value: ['matched atomic'],
},
title: 'matched.atomic',
},
{
description: {
fieldName: 'matched.field',
value: 'matched field',
value: ['matched field'],
},
title: 'matched.field',
},
{
description: {
fieldName: 'matched.type',
value: 'matched type',
value: ['matched type'],
},
title: 'matched.type',
},
Expand All @@ -548,7 +548,7 @@ describe('buildThreatDetailsItems', () => {
title: 'array_values',
description: {
fieldName: 'array_values',
value: 'first value',
value: ['first value', 'second value'],
},
},
]);
Expand All @@ -563,7 +563,7 @@ describe('buildThreatDetailsItems', () => {
title: 'indicator.ip',
description: {
fieldName: 'threat.indicator.ip',
value: '127.0.0.1',
value: ['127.0.0.1'],
},
},
]);
Expand All @@ -579,7 +579,7 @@ describe('buildThreatDetailsItems', () => {
title: 'object_field.foo',
description: {
fieldName: 'object_field.foo',
value: 'bar',
value: ['bar'],
},
},
]);
Expand All @@ -597,8 +597,9 @@ describe('buildThreatDetailsItems', () => {
title: 'flattened_object',
description: {
fieldName: 'flattened_object',
value:
value: [
'This field contains nested object values, which are not rendered here. See the full document for all fields/values',
],
},
},
]);
Expand All @@ -614,8 +615,9 @@ describe('buildThreatDetailsItems', () => {
title: 'array_field',
description: {
fieldName: 'array_field',
value:
value: [
'This field contains nested object values, which are not rendered here. See the full document for all fields/values',
],
},
},
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import {
} from '../../../../../common/constants';
import {
ENRICHMENT_TYPES,
FEED_NAME,
FIRST_SEEN,
MATCHED_ATOMIC,
MATCHED_FIELD,
MATCHED_ID,
MATCHED_TYPE,
FEED_NAME,
} from '../../../../../common/cti/constants';

const NESTED_OBJECT_VALUES_NOT_RENDERED = i18n.translate(
Expand Down Expand Up @@ -189,10 +189,23 @@ export const buildThreatDetailsItems = (enrichment: CtiEnrichment): ThreatDetail
? field.replace(`${DEFAULT_INDICATOR_SOURCE_PATH}`, 'indicator')
: field;

let value = getFirstElement(enrichment[field]);
if (isObject(value)) {
value = NESTED_OBJECT_VALUES_NOT_RENDERED;
}

return { title, description: { fieldName: field, value: value as string } };
// Gather string values, ignoring nested objects/arrays
// TODO We should probably enhance this in the future to show all values, but
// this will require more involved UI changes (like show a table or json view similar to the flyout)
const values: string[] = [];
const enrichmentValues = enrichment[field];
enrichmentValues.forEach((enrichmentValue) => {
// We show the string values as is, but for nested objects we show a message indicating
// that those values are not rendered here
if (typeof enrichmentValue === 'string') {
values.push(enrichmentValue);
} else if (isObject(enrichmentValue)) {
// We don't need to add the message more than once
if (!values.includes(NESTED_OBJECT_VALUES_NOT_RENDERED)) {
values.push(NESTED_OBJECT_VALUES_NOT_RENDERED);
}
}
});

return { title, description: { fieldName: field, value: values as string[] } };
});