diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.test.tsx index 81d971fa98af8..e056eeae67085 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.test.tsx @@ -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( { ); - expect(true).toBeTruthy(); + expect(getByTestId(ENRICHMENT_ACCORDION_LINK_TEST_ID)).toBeInTheDocument(); + }); + + it('should render rows with EuiLink', () => { + const { getAllByTestId, getAllByText, getByText } = render( + + + + ); + + 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(); }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.tsx index 85b443682d34e..1bb366432c34c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.tsx @@ -11,8 +11,8 @@ import { EuiAccordion, EuiFlexGroup, EuiFlexItem, - EuiLink, EuiInMemoryTable, + EuiLink, EuiTitle, EuiToolTip, useEuiTheme, @@ -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'; @@ -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', { @@ -61,7 +66,7 @@ export interface ThreatDetailsRow { /** * Value of the enrichment */ - value: string; + value: string[]; }; } @@ -70,7 +75,7 @@ const columns: Array> = [ field: 'title', truncateText: false, render: (title: string) => ( - + {title} ), @@ -82,13 +87,27 @@ const columns: Array> = [ truncateText: false, render: (description: ThreatDetailsRow['description']) => { const { fieldName, value } = description; - const tooltipChild = fieldName.match(REFERENCE) ? ( - - {value} - + + const renderedValue = fieldName.match(REFERENCE) ? ( + + {value.map((val, key) => ( + + + {val} + + + ))} + ) : ( - {value} + + {value.map((val, key) => ( + + {val} + + ))} + ); + return ( > = [ } > - {tooltipChild} + {renderedValue} ); }, @@ -137,6 +156,7 @@ export const EnrichmentAccordion = memo(({ enrichment, index }: EnrichmentAccord return ( { @@ -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', }, @@ -548,7 +548,7 @@ describe('buildThreatDetailsItems', () => { title: 'array_values', description: { fieldName: 'array_values', - value: 'first value', + value: ['first value', 'second value'], }, }, ]); @@ -563,7 +563,7 @@ describe('buildThreatDetailsItems', () => { title: 'indicator.ip', description: { fieldName: 'threat.indicator.ip', - value: '127.0.0.1', + value: ['127.0.0.1'], }, }, ]); @@ -579,7 +579,7 @@ describe('buildThreatDetailsItems', () => { title: 'object_field.foo', description: { fieldName: 'object_field.foo', - value: 'bar', + value: ['bar'], }, }, ]); @@ -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', + ], }, }, ]); @@ -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', + ], }, }, ]); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.tsx index fefacf3e7fc14..8047d988577e8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.tsx @@ -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( @@ -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[] } }; });