[Security Solution] add highlighted fields to overview tab (#153074)

This commit is contained in:
Philippe Oberti 2023-03-21 19:00:46 -04:00 committed by GitHub
parent b345af2a62
commit 123401d6f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 413 additions and 4 deletions

View file

@ -8,6 +8,13 @@
import {
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK,
DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT,
DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE,
} from '../../../screens/document_expandable_flyout';
import {
expandFirstAlertExpandableFlyout,
@ -40,5 +47,41 @@ describe.skip(
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE).should('be.visible');
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS).should('be.visible');
});
it('should display highlighted fields', () => {
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS).within(() => {
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON)
.should('be.visible')
.click();
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE)
.should('be.visible')
.and('have.text', 'Highlighted fields');
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS).should(
'be.visible'
);
// close highlighted fields to reset the view for next test
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON)
.should('be.visible')
.click();
});
});
it('should navigate to table tab when clicking on highlighted fields view button', () => {
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS).within(() => {
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON)
.should('be.visible')
.click();
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK)
.should('be.visible')
.click();
});
// 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');
});
}
);

View file

@ -37,6 +37,11 @@ import {
COLLAPSE_DETAILS_BUTTON_TEST_ID,
EXPAND_DETAILS_BUTTON_TEST_ID,
FLYOUT_HEADER_TITLE_TEST_ID,
HIGHLIGHTED_FIELDS_DETAILS_TEST_ID,
HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK,
HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON_TEST_ID,
HIGHLIGHTED_FIELDS_HEADER_TITLE_TEST_ID,
HIGHLIGHTED_FIELDS_TEST_ID,
MITRE_ATTACK_DETAILS_TEST_ID,
MITRE_ATTACK_TITLE_TEST_ID,
} from '../../public/flyout/right/components/test_ids';
@ -103,6 +108,17 @@ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE = getDataTe
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS = getDataTestSubjectSelector(
MITRE_ATTACK_DETAILS_TEST_ID
);
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS = getDataTestSubjectSelector(
HIGHLIGHTED_FIELDS_TEST_ID
);
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON =
getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON_TEST_ID);
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE =
getDataTestSubjectSelector(HIGHLIGHTED_FIELDS_HEADER_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);
/* Table tab */

View file

@ -83,7 +83,7 @@ const SummaryViewComponent: React.FC<{
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink onClick={goToTable}>
<EuiLink onClick={goToTable} data-test-subj="summary-view-go-to-table-link">
<EuiText size="xs">{VIEW_ALL_FIELDS}</EuiText>
</EuiLink>
</EuiFlexItem>

View file

@ -0,0 +1,75 @@
/*
* 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 type { Story } from '@storybook/react';
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
import { HighlightedFields } from './highlighted_fields';
import { RightPanelContext } from '../context';
export default {
component: HighlightedFields,
title: 'Flyout/HighlightedFields',
};
// TODO ideally we would want to have some data here, but we need to spent some time getting all the foundation items for storybook
// (ReduxStoreProvider, CellActionsProvider...) similarly to how it was done for the TestProvidersComponent
// see ticket https://github.com/elastic/security-team/issues/6223
export const Expanded: Story<void> = () => {
const flyoutContextValue = {
openRightPanel: () => window.alert('openRightPanel called'),
} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: 'eventId',
indexName: 'indexName',
dataFormattedForFieldBrowser: [],
browserFields: {},
} as unknown as RightPanelContext;
return (
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields expanded={true} />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
);
};
export const Collapsed: Story<void> = () => {
const flyoutContextValue = {
openRightPanel: () => window.alert('openRightPanel called'),
} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: 'eventId',
indexName: 'indexName',
dataFormattedForFieldBrowser: [],
browserFields: {},
} as unknown as RightPanelContext;
return (
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
);
};
export const Emtpy: Story<void> = () => {
const flyoutContextValue = {} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: null,
} as unknown as RightPanelContext;
return (
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
);
};

View file

@ -0,0 +1,184 @@
/*
* 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 { RightPanelContext } from '../context';
import {
HIGHLIGHTED_FIELDS_DETAILS_TEST_ID,
HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK,
HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON_TEST_ID,
HIGHLIGHTED_FIELDS_HEADER_TITLE_TEST_ID,
} from './test_ids';
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
import { HighlightedFields } from './highlighted_fields';
import { RightPanelKey, RightPanelTableTabPath } from '..';
import { getMockTheme } from '../../../common/lib/kibana/kibana_react.mock';
import { ThemeProvider } from 'styled-components';
const mockTheme = getMockTheme({
eui: {
euiSizeL: '10px',
},
});
describe('<HighlightedFields />', () => {
it('should render the component collapsed', () => {
const flyoutContextValue = {
openRightPanel: jest.fn(),
} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: 'eventId',
indexName: 'indexName',
dataFormattedForFieldBrowser: [],
browserFields: {},
} as unknown as RightPanelContext;
const { getByTestId } = render(
<ThemeProvider theme={mockTheme}>
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
</ThemeProvider>
);
expect(getByTestId(HIGHLIGHTED_FIELDS_HEADER_TITLE_TEST_ID)).toBeInTheDocument();
});
it('should render the component expanded', () => {
const flyoutContextValue = {
openRightPanel: jest.fn(),
} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: 'eventId',
indexName: 'indexName',
dataFormattedForFieldBrowser: [],
browserFields: {},
} as unknown as RightPanelContext;
const { getByTestId } = render(
<ThemeProvider theme={mockTheme}>
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields expanded={true} />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
</ThemeProvider>
);
expect(getByTestId(HIGHLIGHTED_FIELDS_HEADER_TITLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(HIGHLIGHTED_FIELDS_DETAILS_TEST_ID)).toBeInTheDocument();
});
it('should expand details when clicking on header', () => {
const flyoutContextValue = {
openRightPanel: jest.fn(),
} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: 'eventId',
indexName: 'indexName',
dataFormattedForFieldBrowser: [],
browserFields: {},
} as unknown as RightPanelContext;
const { getByTestId } = render(
<ThemeProvider theme={mockTheme}>
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
</ThemeProvider>
);
getByTestId(HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON_TEST_ID).click();
getByTestId(HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK).click();
expect(flyoutContextValue.openRightPanel).toHaveBeenCalledWith({
id: RightPanelKey,
path: RightPanelTableTabPath,
params: {
id: panelContextValue.eventId,
indexName: panelContextValue.indexName,
},
});
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 { baseElement } = render(
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
);
expect(baseElement).toMatchInlineSnapshot(`
<body>
<div />
</body>
`);
});
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 { baseElement } = render(
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
);
expect(baseElement).toMatchInlineSnapshot(`
<body>
<div />
</body>
`);
});
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 { baseElement } = render(
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
<HighlightedFields />
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
);
expect(baseElement).toMatchInlineSnapshot(`
<body>
<div />
</body>
`);
});
});

View file

@ -0,0 +1,73 @@
/*
* 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 { EuiFlexGroup } from '@elastic/eui';
import type { VFC } from 'react';
import React, { useCallback, useState } from 'react';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { HIGHLIGHTED_FIELDS_TEST_ID } from './test_ids';
import { AlertSummaryView } from '../../../common/components/event_details/alert_summary_view';
import { HIGHLIGHTED_FIELDS_TITLE } from './translations';
import { HeaderSection } from '../../../common/components/header_section';
import { useRightPanelContext } from '../context';
import { RightPanelKey, RightPanelTableTabPath } from '..';
export interface HighlightedFieldsProps {
/**
* Boolean to allow the component to be expanded or collapsed on first render
*/
expanded?: boolean;
}
export const HighlightedFields: VFC<HighlightedFieldsProps> = ({ expanded = false }) => {
const [isPanelExpanded, setIsPanelExpanded] = useState(expanded);
const { openRightPanel } = useExpandableFlyoutContext();
const { eventId, indexName, dataFormattedForFieldBrowser, browserFields } =
useRightPanelContext();
const goToTableTab = useCallback(() => {
openRightPanel({
id: RightPanelKey,
path: RightPanelTableTabPath,
params: {
id: eventId,
indexName,
},
});
}, [eventId, indexName, openRightPanel]);
if (!dataFormattedForFieldBrowser || !browserFields || !eventId) {
return <></>;
}
return (
<EuiFlexGroup gutterSize="none" direction="column" data-test-subj={HIGHLIGHTED_FIELDS_TEST_ID}>
<HeaderSection
alignHeader="center"
hideSubtitle
outerDirection="row"
title={HIGHLIGHTED_FIELDS_TITLE}
titleSize="xs"
toggleQuery={setIsPanelExpanded}
toggleStatus={isPanelExpanded}
/>
{isPanelExpanded && (
<AlertSummaryView
data={dataFormattedForFieldBrowser}
eventId={eventId}
browserFields={browserFields}
isDraggable={false}
scopeId={'global'}
title={HIGHLIGHTED_FIELDS_TITLE}
isReadOnly={false} // TODO: set properly
goToTable={goToTableTab}
/>
)}
</EuiFlexGroup>
);
};

View file

@ -12,3 +12,8 @@ export const COLLAPSE_DETAILS_BUTTON_TEST_ID =
'securitySolutionDocumentDetailsFlyoutHeaderCollapseDetailButton';
export const MITRE_ATTACK_TITLE_TEST_ID = 'securitySolutionAlertDetailsFlyoutMitreAttackTitle';
export const MITRE_ATTACK_DETAILS_TEST_ID = 'securitySolutionAlertDetailsFlyoutMitreAttackDetails';
export const HIGHLIGHTED_FIELDS_TEST_ID = 'securitySolutionDocumentDetailsFlyoutHighlightedFields';
export const HIGHLIGHTED_FIELDS_HEADER_EXPAND_ICON_TEST_ID = 'query-toggle-header';
export const HIGHLIGHTED_FIELDS_HEADER_TITLE_TEST_ID = 'header-section-title';
export const HIGHLIGHTED_FIELDS_DETAILS_TEST_ID = 'summary-view';
export const HIGHLIGHTED_FIELDS_GO_TO_TABLE_LINK = 'summary-view-go-to-table-link';

View file

@ -16,3 +16,8 @@ export const COLLAPSE_DETAILS_BUTTON = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.collapseDetailButton',
{ defaultMessage: 'Collapse alert details' }
);
export const HIGHLIGHTED_FIELDS_TITLE = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.highlightedFieldsTitle',
{ defaultMessage: 'Highlighted fields' }
);

View file

@ -18,6 +18,7 @@ import { tabs } from './tabs';
export type RightPanelPaths = 'overview' | 'table' | 'json';
export const RightPanelKey: RightPanelProps['key'] = 'document-details-right';
export const RightPanelTableTabPath: RightPanelProps['path'] = ['table'];
export interface RightPanelProps extends FlyoutPanel {
key: 'document-details-right';

View file

@ -7,13 +7,22 @@
import type { FC } from 'react';
import React, { memo } from 'react';
import { EuiPanel } from '@elastic/eui';
import { HighlightedFields } from '../components/highlighted_fields';
import { MitreAttack } from '../components/mitre_attack';
/**
* Overview view displayed in the document details expandable flyout right section
*/
export const OverviewTab: FC = memo(() => {
return <MitreAttack />;
return (
<>
<MitreAttack />
<EuiPanel hasBorder hasShadow={false}>
<HighlightedFields />
</EuiPanel>
</>
);
});
OverviewTab.displayName = 'OverviewTab';

View file

@ -5,8 +5,6 @@
* 2.0.
*/
export const OVERVIEW_TAB_CONTENT_TEST_ID =
'securitySolutionDocumentDetailsFlyoutOverviewTabContent';
export const TABLE_TAB_CONTENT_TEST_ID = 'event-fields-browser';
export const TABLE_TAB_ERROR_TEST_ID = 'securitySolutionAlertDetailsFlyoutTableTabError';
export const JSON_TAB_CONTENT_TEST_ID = 'jsonView';