Skip to content

Commit

Permalink
[Security Solution] expandable flyout - highlighted fields enhancemen…
Browse files Browse the repository at this point in the history
…ts (#162417)
  • Loading branch information
PhilippeOberti authored Jul 28, 2023
1 parent a44df51 commit 8543e83
Show file tree
Hide file tree
Showing 11 changed files with 439 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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',
Expand Down Expand Up @@ -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');

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');

// 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');
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');
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 */
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
);

expect(getByTestId(HIGHLIGHTED_FIELDS_TITLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(HIGHLIGHTED_FIELDS_DETAILS_TEST_ID)).toBeInTheDocument();
});

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;

const { container } = render(
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<TestProviders>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
</TestProviders>
);

expect(container).toBeEmptyDOMElement();
expect(getByTestId(HIGHLIGHTED_FIELDS_TITLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(HIGHLIGHTED_FIELDS_DETAILS_TEST_ID)).toBeInTheDocument();
});

it('should render empty component if browserFields is null', () => {
const flyoutContextValue = {} as unknown as ExpandableFlyoutContext;
it(`should render empty component if there aren't any highlighted fields`, () => {
const panelContextValue = {
eventId: 'eventId',
indexName: 'indexName',
dataFormattedForFieldBrowser: [],
browserFields: null,
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
} as unknown as RightPanelContext;
(useHighlightedFields as jest.Mock).mockReturnValue([]);

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();
});

it('should render empty component if eventId is null', () => {
const flyoutContextValue = {} as unknown as ExpandableFlyoutContext;
it('should render empty component if dataFormattedForFieldBrowser is null', () => {
const panelContextValue = {
eventId: null,
indexName: 'indexName',
dataFormattedForFieldBrowser: [],
browserFields: {},
dataFormattedForFieldBrowser: null,
} 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>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
);

expect(container).toBeEmptyDOMElement();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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>
Expand Down
Loading

0 comments on commit 8543e83

Please sign in to comment.