[Security Solution] expandable flyout - highlighted fields enhancements (#162417)

This commit is contained in:
Philippe Oberti 2023-07-28 08:47:53 +02:00 committed by GitHub
parent a44df515f9
commit 8543e8366b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 447 additions and 126 deletions

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { collapseDocumentDetailsExpandableFlyoutLeftSection } from '../../../../tasks/expandable_flyout/alert_details_right_panel';
import { DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT } from '../../../../screens/expandable_flyout/alert_details_left_panel_investigation_tab';
import {
createNewCaseFromExpandableFlyout,
@ -19,7 +20,6 @@ import {
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_OPEN_RULE_PREVIEW_BUTTON,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER,
@ -42,6 +42,8 @@ import {
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL,
} from '../../../../screens/expandable_flyout/alert_details_right_panel_overview_tab';
import {
clickCorrelationsViewAllButton,
@ -61,10 +63,10 @@ import { getNewRule } from '../../../../objects/rule';
import { ALERTS_URL } from '../../../../urls/navigation';
import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule';
import {
DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT,
DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW,
} from '../../../../screens/expandable_flyout/alert_details_right_panel_table_tab';
import { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT } from '../../../../screens/expandable_flyout/alert_details_left_panel_entities_tab';
DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT,
DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS,
DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS,
} from '../../../../screens/expandable_flyout/alert_details_left_panel_entities_tab';
describe(
'Alert details expandable flyout right panel overview tab',
@ -191,17 +193,29 @@ describe(
'be.visible'
);
cy.log('navigate to table tab when clicking on highlighted fields view button');
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK)
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL)
.should('be.visible')
.click();
.and('contain.text', 'host.name');
const hostNameCell =
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL('siem-kibana');
cy.get(hostNameCell).should('be.visible').and('have.text', 'siem-kibana');
// the table component is rendered within a dom element with overflow, so Cypress isn't finding it
// this next line is a hack that scrolls to a specific element in the table
// (in the middle of it vertically) to ensure Cypress finds it
cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW).scrollIntoView();
cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT).should('be.visible');
cy.get(hostNameCell).click();
cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).scrollIntoView();
cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).should('be.visible');
collapseDocumentDetailsExpandableFlyoutLeftSection();
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL)
.should('be.visible')
.and('contain.text', 'user.name');
const userNameCell =
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL('test');
cy.get(userNameCell).should('be.visible').and('have.text', 'test');
cy.get(userNameCell).click();
cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).scrollIntoView();
cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).should('be.visible');
});
});

View file

@ -20,7 +20,6 @@ import {
ENTITY_PANEL_CONTENT_TEST_ID,
ENTITY_PANEL_HEADER_TEST_ID,
HIGHLIGHTED_FIELDS_DETAILS_TEST_ID,
HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK,
HIGHLIGHTED_FIELDS_TITLE_TEST_ID,
INSIGHTS_CORRELATIONS_CONTENT_TEST_ID,
INSIGHTS_CORRELATIONS_TITLE_TEST_ID,
@ -44,6 +43,7 @@ import {
REASON_TITLE_TEST_ID,
SESSION_PREVIEW_TEST_ID,
VISUALIZATIONS_SECTION_HEADER_TEST_ID,
HIGHLIGHTED_FIELDS_CELL_TEST_ID,
} from '../../../public/flyout/right/components/test_ids';
/* About section */
@ -83,8 +83,11 @@ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITL
getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_TITLE_TEST_ID);
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS =
getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_DETAILS_TEST_ID);
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK =
getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK);
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL =
getDataTestSubjectSelector('fieldCell');
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL = (
value: string
) => getDataTestSubjectSelector(`${value}-${HIGHLIGHTED_FIELDS_CELL_TEST_ID}`);
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON =
getDataTestSubjectSelector(INVESTIGATION_GUIDE_BUTTON_TEST_ID);

View file

@ -22,14 +22,18 @@ import {
/**
* Expand the left section of the document details expandable flyout by clicking on the expand icon button
*/
export const expandDocumentDetailsExpandableFlyoutLeftSection = () =>
export const expandDocumentDetailsExpandableFlyoutLeftSection = () => {
cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON).scrollIntoView();
cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON).click();
};
/**
* Expand the left section of the document details expandable flyout by clicking on the collapse icon button
*/
export const collapseDocumentDetailsExpandableFlyoutLeftSection = () =>
export const collapseDocumentDetailsExpandableFlyoutLeftSection = () => {
cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON).scrollIntoView();
cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON).click();
};
/**
* Open the Overview tab in the document details expandable flyout right section

View file

@ -217,7 +217,7 @@ function getFieldsByRuleType(ruleType?: string): EventSummaryField[] {
/**
This function is exported because it is used in the Exception Component to
populate the conditions with the Highlighted Fields. Additionally, the new
populate the conditions with the Highlighted Fields. Additionally, the new
Alert Summary Flyout also requires access to these fields.
As the Alert Summary components will undergo changes soon we will go with
exporting the function only for now.
@ -255,7 +255,7 @@ interface EventCategories {
* @param data The event details
* @returns The event's primary category and all other categories in case there is more than one
*/
function getEventCategoriesFromData(data: TimelineEventsDetailsItem[]): EventCategories {
export function getEventCategoriesFromData(data: TimelineEventsDetailsItem[]): EventCategories {
const eventCategoryField = find({ category: 'event', field: 'event.category' }, data);
let primaryEventCategory: string | undefined;

View file

@ -9,97 +9,73 @@ import React from 'react';
import { render } from '@testing-library/react';
import { RightPanelContext } from '../context';
import { HIGHLIGHTED_FIELDS_DETAILS_TEST_ID, HIGHLIGHTED_FIELDS_TITLE_TEST_ID } from './test_ids';
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
import { HighlightedFields } from './highlighted_fields';
import { getMockTheme } from '../../../common/lib/kibana/kibana_react.mock';
import { ThemeProvider } from 'styled-components';
import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context';
import { useHighlightedFields } from '../hooks/use_highlighted_fields';
import { TestProviders } from '../../../common/mock';
jest.mock('../hooks/use_highlighted_fields');
describe('<HighlightedFields />', () => {
it('should render the component', () => {
const mockTheme = getMockTheme({
eui: {
euiSizeL: '10px',
},
});
const flyoutContextValue = {
openRightPanel: jest.fn(),
} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: 'eventId',
indexName: 'indexName',
dataFormattedForFieldBrowser: [],
browserFields: {},
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
} as unknown as RightPanelContext;
(useHighlightedFields as jest.Mock).mockReturnValue([
{
field: 'field',
description: {
field: 'field',
values: ['value'],
},
},
]);
const { getByTestId } = render(
<ThemeProvider theme={mockTheme}>
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
</ThemeProvider>
<TestProviders>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</TestProviders>
);
expect(getByTestId(HIGHLIGHTED_FIELDS_TITLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(HIGHLIGHTED_FIELDS_DETAILS_TEST_ID)).toBeInTheDocument();
});
it(`should render empty component if there aren't any highlighted fields`, () => {
const panelContextValue = {
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
} as unknown as RightPanelContext;
(useHighlightedFields as jest.Mock).mockReturnValue([]);
const { container } = render(
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
);
expect(container).toBeEmptyDOMElement();
});
it('should render empty component if dataFormattedForFieldBrowser is null', () => {
const flyoutContextValue = {} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: 'eventId',
indexName: 'indexName',
dataFormattedForFieldBrowser: null,
browserFields: {},
} as unknown as RightPanelContext;
(useHighlightedFields as jest.Mock).mockReturnValue([
{
field: 'field',
description: {
field: 'field',
values: ['value'],
},
},
]);
const { container } = render(
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
);
expect(container).toBeEmptyDOMElement();
});
it('should render empty component if browserFields is null', () => {
const flyoutContextValue = {} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: 'eventId',
indexName: 'indexName',
dataFormattedForFieldBrowser: [],
browserFields: null,
} as unknown as RightPanelContext;
const { container } = render(
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
);
expect(container).toBeEmptyDOMElement();
});
it('should render empty component if eventId is null', () => {
const flyoutContextValue = {} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: null,
indexName: 'indexName',
dataFormattedForFieldBrowser: [],
browserFields: {},
} as unknown as RightPanelContext;
const { container } = render(
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
);
expect(container).toBeEmptyDOMElement();

View file

@ -6,38 +6,61 @@
*/
import type { FC } from 'react';
import React, { useCallback } from 'react';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui';
import React from 'react';
import type { EuiBasicTableColumn } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, EuiPanel, EuiTitle } from '@elastic/eui';
import { HighlightedFieldsCell } from './highlighted_fields_cell';
import {
CellActionsMode,
SecurityCellActions,
SecurityCellActionsTrigger,
} from '../../../common/components/cell_actions';
import type { UseHighlightedFieldsResult } from '../hooks/use_highlighted_fields';
import { HIGHLIGHTED_FIELDS_DETAILS_TEST_ID, HIGHLIGHTED_FIELDS_TITLE_TEST_ID } from './test_ids';
import { AlertSummaryView } from '../../../common/components/event_details/alert_summary_view';
import { HIGHLIGHTED_FIELDS_TITLE } from './translations';
import {
HIGHLIGHTED_FIELDS_FIELD_COLUMN,
HIGHLIGHTED_FIELDS_TITLE,
HIGHLIGHTED_FIELDS_VALUE_COLUMN,
} from './translations';
import { useRightPanelContext } from '../context';
import { RightPanelKey, RightPanelTableTabPath } from '..';
import { useHighlightedFields } from '../hooks/use_highlighted_fields';
const columns: Array<EuiBasicTableColumn<UseHighlightedFieldsResult>> = [
{
field: 'field',
name: HIGHLIGHTED_FIELDS_FIELD_COLUMN,
'data-test-subj': 'fieldCell',
width: '125px',
},
{
field: 'description',
name: HIGHLIGHTED_FIELDS_VALUE_COLUMN,
'data-test-subj': 'valueCell',
render: (description: { field: string; values: string[] | null | undefined }) => (
<SecurityCellActions
data={{
field: description.field,
value: description.values,
}}
mode={CellActionsMode.HOVER_RIGHT}
triggerId={SecurityCellActionsTrigger.DEFAULT}
visibleCellActions={6}
>
<HighlightedFieldsCell values={description.values} field={description.field} />
</SecurityCellActions>
),
},
];
/**
* Component that displays the highlighted fields in the right panel under the Investigation section.
* It leverages the existing {@link AlertSummaryView} component.
* // TODO will require improvements https://github.com/elastic/security-team/issues/6428
*/
export const HighlightedFields: FC = () => {
const { openRightPanel } = useExpandableFlyoutContext();
const { eventId, indexName, dataFormattedForFieldBrowser, browserFields, scopeId } =
useRightPanelContext();
const { dataFormattedForFieldBrowser } = useRightPanelContext();
const goToTableTab = useCallback(() => {
openRightPanel({
id: RightPanelKey,
path: RightPanelTableTabPath,
params: {
id: eventId,
indexName,
scopeId,
},
});
}, [eventId, indexName, openRightPanel, scopeId]);
const highlightedFields = useHighlightedFields({ dataFormattedForFieldBrowser });
if (!dataFormattedForFieldBrowser || !browserFields || !eventId) {
if (!dataFormattedForFieldBrowser || highlightedFields.length === 0) {
return null;
}
@ -50,16 +73,7 @@ export const HighlightedFields: FC = () => {
</EuiFlexItem>
<EuiFlexItem data-test-subj={HIGHLIGHTED_FIELDS_DETAILS_TEST_ID}>
<EuiPanel hasBorder hasShadow={false}>
<AlertSummaryView // TODO consider using x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx instead if the link to the table have to be removed
data={dataFormattedForFieldBrowser}
eventId={eventId}
browserFields={browserFields}
isDraggable={false}
scopeId={scopeId}
title={''}
isReadOnly={false} // TODO: set properly
goToTable={goToTableTab}
/>
<EuiInMemoryTable items={highlightedFields} columns={columns} compressed />
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -0,0 +1,88 @@
/*
* 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 {
HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID,
HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID,
HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID,
} from './test_ids';
import { HighlightedFieldsCell } from './highlighted_fields_cell';
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
import { RightPanelContext } from '../context';
import { LeftPanelInsightsTabPath, LeftPanelKey } from '../../left';
import { TestProviders } from '../../../common/mock';
const flyoutContextValue = {
openLeftPanel: jest.fn(),
} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: 'event id',
indexName: 'indexName',
scopeId: 'scopeId',
} as unknown as RightPanelContext;
const renderHighlightedFieldsCell = (values: string[], field: string) =>
render(
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFieldsCell values={values} field={field} />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
);
describe('<HighlightedFieldsCell />', () => {
it('should render a basic cell', () => {
const { getByTestId } = render(<HighlightedFieldsCell values={['value']} field={'field'} />);
expect(getByTestId(HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID)).toBeInTheDocument();
});
it('should render a link cell if field is host.name', () => {
const { getByTestId } = renderHighlightedFieldsCell(['value'], 'host.name');
expect(getByTestId(HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID)).toBeInTheDocument();
});
it('should render a link cell if field is user.name', () => {
const { getByTestId } = renderHighlightedFieldsCell(['value'], 'user.name');
expect(getByTestId(HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID)).toBeInTheDocument();
});
it('should open left panel when clicking on the link within a a link cell', () => {
const { getByTestId } = renderHighlightedFieldsCell(['value'], 'user.name');
getByTestId(HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID).click();
expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({
id: LeftPanelKey,
path: LeftPanelInsightsTabPath,
params: {
id: panelContextValue.eventId,
indexName: panelContextValue.indexName,
scopeId: panelContextValue.scopeId,
},
});
});
it('should render agent status cell if field is agent.status', () => {
const { getByTestId } = render(
<TestProviders>
<HighlightedFieldsCell values={['value']} field={'agent.status'} />
</TestProviders>
);
expect(getByTestId(HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID)).toBeInTheDocument();
});
it('should render agent status component if override field is agent.status', () => {
const { container } = render(<HighlightedFieldsCell values={null} field={'field'} />);
expect(container).toBeEmptyDOMElement();
});
});

View file

@ -0,0 +1,99 @@
/*
* 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 { VFC } from 'react';
import React, { useCallback } from 'react';
import { EuiFlexItem, EuiLink } from '@elastic/eui';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { EndpointAgentStatusById } from '../../../common/components/endpoint/endpoint_agent_status';
import { useRightPanelContext } from '../context';
import {
AGENT_STATUS_FIELD_NAME,
HOST_NAME_FIELD_NAME,
USER_NAME_FIELD_NAME,
} from '../../../timelines/components/timeline/body/renderers/constants';
import { LeftPanelInsightsTabPath, LeftPanelKey } from '../../left';
import {
HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID,
HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID,
HIGHLIGHTED_FIELDS_CELL_TEST_ID,
HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID,
} from './test_ids';
interface LinkFieldCellProps {
/**
* Highlighted field's value to display as a EuiLink to open the expandable left panel
* (used for host name and username fields)
*/
value: string;
}
/**
* // Currently we can use the same component for both host name and username
*/
const LinkFieldCell: VFC<LinkFieldCellProps> = ({ value }) => {
const { scopeId, eventId, indexName } = useRightPanelContext();
const { openLeftPanel } = useExpandableFlyoutContext();
const goToInsightsEntities = useCallback(() => {
openLeftPanel({
id: LeftPanelKey,
path: LeftPanelInsightsTabPath,
params: {
id: eventId,
indexName,
scopeId,
},
});
}, [eventId, indexName, openLeftPanel, scopeId]);
return (
<EuiLink onClick={goToInsightsEntities} data-test-subj={HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID}>
{value}
</EuiLink>
);
};
export interface HighlightedFieldsCellProps {
/**
* Highlighted field's name used to know what component to display
*/
field: string;
/**
* Highlighted field's value to display
*/
values: string[] | null | undefined;
}
/**
* Renders a component in the highlighted fields table cell based on the field name
*/
export const HighlightedFieldsCell: VFC<HighlightedFieldsCellProps> = ({ values, field }) => (
<>
{values != null &&
values.map((value, i) => {
return (
<EuiFlexItem
grow={false}
key={`${i}-${value}`}
data-test-subj={`${value}-${HIGHLIGHTED_FIELDS_CELL_TEST_ID}`}
>
{field === HOST_NAME_FIELD_NAME || field === USER_NAME_FIELD_NAME ? (
<LinkFieldCell value={value} />
) : field === AGENT_STATUS_FIELD_NAME ? (
<EndpointAgentStatusById
endpointAgentId={String(value ?? '')}
data-test-subj={HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID}
/>
) : (
<span data-test-subj={HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID}>{value}</span>
)}
</EuiFlexItem>
);
})}
</>
);

View file

@ -53,7 +53,15 @@ export const HIGHLIGHTED_FIELDS_TITLE_TEST_ID =
'securitySolutionDocumentDetailsFlyoutHighlightedFieldsTitle';
export const HIGHLIGHTED_FIELDS_DETAILS_TEST_ID =
'securitySolutionDocumentDetailsFlyoutHighlightedFieldsDetails';
export const HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK = 'summary-view-go-to-table-link';
export const HIGHLIGHTED_FIELDS_CELL_TEST_ID =
'securitySolutionDocumentDetailsFlyoutHighlightedFieldsCell';
export const HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID =
'securitySolutionDocumentDetailsFlyoutHighlightedFieldsBasicCell';
export const HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID =
'securitySolutionDocumentDetailsFlyoutHighlightedFieldsLinkedCell';
export const HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID =
'securitySolutionDocumentDetailsFlyoutHighlightedFieldsAgentStatusCell';
export const INVESTIGATION_GUIDE_BUTTON_TEST_ID =
'securitySolutionDocumentDetailsFlyoutInvestigationGuideButton';

View file

@ -120,6 +120,16 @@ export const HIGHLIGHTED_FIELDS_TITLE = i18n.translate(
{ defaultMessage: 'Highlighted fields' }
);
export const HIGHLIGHTED_FIELDS_FIELD_COLUMN = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.highlightedFields.fieldColumn',
{ defaultMessage: 'Field' }
);
export const HIGHLIGHTED_FIELDS_VALUE_COLUMN = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.highlightedFields.valueColumn',
{ defaultMessage: 'Value' }
);
/* Insights section */
export const ENTITIES_TITLE = i18n.translate(

View file

@ -0,0 +1,105 @@
/*
* 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 { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import { find, isEmpty } from 'lodash/fp';
import { ALERT_RULE_TYPE } from '@kbn/rule-data-utils';
import { isAlertFromEndpointEvent } from '../../../common/utils/endpoint_alert_check';
import {
getEventCategoriesFromData,
getEventFieldsToDisplay,
} from '../../../common/components/event_details/get_alert_summary_rows';
export interface UseHighlightedFieldsParams {
/**
* An array of field objects with category and value
*/
dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null;
}
export interface UseHighlightedFieldsResult {
/**
* Highlighted field name (label or if null, falls back to id)
*/
field: string;
description: {
/**
* Highlighted field name (overrideField or if null, falls back to id)
*/
field: string;
/**
* Highlighted field value
*/
values: string[] | null | undefined;
};
}
/**
* Returns the highlighted fields to display in the right panel under the Investigation section.
*/
export const useHighlightedFields = ({
dataFormattedForFieldBrowser,
}: UseHighlightedFieldsParams): UseHighlightedFieldsResult[] => {
if (!dataFormattedForFieldBrowser) return [];
const eventCategories = getEventCategoriesFromData(dataFormattedForFieldBrowser);
const eventCodeField = find(
{ category: 'event', field: 'event.code' },
dataFormattedForFieldBrowser
);
const eventCode = Array.isArray(eventCodeField?.originalValue)
? eventCodeField?.originalValue?.[0]
: eventCodeField?.originalValue;
const eventRuleTypeField = find(
{ category: 'kibana', field: ALERT_RULE_TYPE },
dataFormattedForFieldBrowser
);
const eventRuleType = Array.isArray(eventRuleTypeField?.originalValue)
? eventRuleTypeField?.originalValue?.[0]
: eventRuleTypeField?.originalValue;
const tableFields = getEventFieldsToDisplay({
eventCategories,
eventCode,
eventRuleType,
});
return tableFields.reduce<UseHighlightedFieldsResult[]>((acc, field) => {
const item = dataFormattedForFieldBrowser.find(
(data) => data.field === field.id || (field.legacyId && data.field === field.legacyId)
);
if (!item || isEmpty(item.values)) {
return acc;
}
// If we found the data by its legacy id we swap the ids to display the correct one
if (item.field === field.legacyId) {
field.id = field.legacyId;
}
if (
field.id === 'agent.id' &&
!isAlertFromEndpointEvent({ data: dataFormattedForFieldBrowser })
) {
return acc;
}
return [
...acc,
{
field: field.label ?? field.id,
description: {
field: field.overrideField ?? field.id,
values: item.values,
},
},
];
}, []);
};