diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts index 721de71fc9fd0..7c93af0b211e7 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts @@ -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'); + }); } ); diff --git a/x-pack/plugins/security_solution/cypress/screens/document_expandable_flyout.ts b/x-pack/plugins/security_solution/cypress/screens/document_expandable_flyout.ts index 9af6f601c6a14..00e0c33c28c4a 100644 --- a/x-pack/plugins/security_solution/cypress/screens/document_expandable_flyout.ts +++ b/x-pack/plugins/security_solution/cypress/screens/document_expandable_flyout.ts @@ -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 */ diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx index 188047e2d8ce0..d4ddc993a9fc6 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx @@ -83,7 +83,7 @@ const SummaryViewComponent: React.FC<{ - + {VIEW_ALL_FIELDS} diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.stories.tsx new file mode 100644 index 0000000000000..b0adfdfeb4d67 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.stories.tsx @@ -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 = () => { + const flyoutContextValue = { + openRightPanel: () => window.alert('openRightPanel called'), + } as unknown as ExpandableFlyoutContext; + const panelContextValue = { + eventId: 'eventId', + indexName: 'indexName', + dataFormattedForFieldBrowser: [], + browserFields: {}, + } as unknown as RightPanelContext; + + return ( + + + + + + ); +}; + +export const Collapsed: Story = () => { + const flyoutContextValue = { + openRightPanel: () => window.alert('openRightPanel called'), + } as unknown as ExpandableFlyoutContext; + const panelContextValue = { + eventId: 'eventId', + indexName: 'indexName', + dataFormattedForFieldBrowser: [], + browserFields: {}, + } as unknown as RightPanelContext; + + return ( + + + + + + ); +}; + +export const Emtpy: Story = () => { + const flyoutContextValue = {} as unknown as ExpandableFlyoutContext; + const panelContextValue = { + eventId: null, + } as unknown as RightPanelContext; + + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx new file mode 100644 index 0000000000000..5846303ff6dab --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx @@ -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('', () => { + 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( + + + + + + + + ); + + 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( + + + + + + + + ); + + 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( + + + + + + + + ); + + 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( + + + + + + ); + + expect(baseElement).toMatchInlineSnapshot(` + +
+ + `); + }); + + 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( + + + + + + ); + + expect(baseElement).toMatchInlineSnapshot(` + +
+ + `); + }); + + 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( + + + + + + ); + + expect(baseElement).toMatchInlineSnapshot(` + +
+ + `); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx new file mode 100644 index 0000000000000..f8f7ed46f505f --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx @@ -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 = ({ 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 ( + + + {isPanelExpanded && ( + + )} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts index b679ef7615e4d..a13de2fbf7d64 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts @@ -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'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts b/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts index 8fc7f2b482589..34c39f2c65179 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts @@ -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' } +); diff --git a/x-pack/plugins/security_solution/public/flyout/right/index.tsx b/x-pack/plugins/security_solution/public/flyout/right/index.tsx index 9519776c07993..33489fd6b143a 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/index.tsx @@ -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'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/overview_tab.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/overview_tab.tsx index 3681f738f7ed2..0161f198f097f 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/overview_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/overview_tab.tsx @@ -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 ; + return ( + <> + + + + + + ); }); OverviewTab.displayName = 'OverviewTab'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/right/tabs/test_ids.ts index 858cb6762fa8c..b7245bc96cb83 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/test_ids.ts @@ -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';