From b23e57bc933c40634f391d19cc189fc31503be8c Mon Sep 17 00:00:00 2001 From: Philippe Oberti Date: Mon, 20 Mar 2023 15:29:30 -0400 Subject: [PATCH] [Security Solution] expanded flyout - right section - json tab implementation (#152935) --- .../alert_details_right_panel.cy.ts | 7 ++- .../cypress/helpers/common.ts | 2 +- .../screens/document_expandable_flyout.ts | 8 ++-- .../tasks/document_expandable_flyout.ts | 8 ++++ .../public/flyout/right/context.tsx | 15 +++++- .../flyout/right/tabs/json_tab.stories.tsx | 42 ++++++++++++++++ .../flyout/right/tabs/json_tab.test.tsx | 48 +++++++++++++++++++ .../public/flyout/right/tabs/json_tab.tsx | 23 +++++++-- .../public/flyout/right/tabs/test_ids.ts | 3 +- .../public/flyout/right/tabs/translations.ts | 20 ++++++++ 10 files changed, 163 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.stories.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/right/tabs/translations.ts diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel.cy.ts index 2c730942494c1..6b3be87473dae 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/expandable_flyout/alert_details_right_panel.cy.ts @@ -12,7 +12,6 @@ import { DOCUMENT_DETAILS_FLYOUT_JSON_TAB, DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_CONTENT, DOCUMENT_DETAILS_FLYOUT_TABLE_TAB, DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT, } from '../../../screens/document_expandable_flyout'; @@ -23,6 +22,7 @@ import { openJsonTab, openOverviewTab, openTableTab, + scrollWithinDocumentDetailsExpandableFlyoutRightSection, } from '../../../tasks/document_expandable_flyout'; import { cleanKibana } from '../../../tasks/common'; import { login, visit } from '../../../tasks/login'; @@ -67,12 +67,15 @@ describe.skip('Alert details expandable flyout right panel', { testIsolation: fa it('should display tab content when switching tabs in the right section', () => { openOverviewTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_CONTENT).should('be.visible'); + // we shouldn't need to test anything here as it's covered with the new overview_tab file openTableTab(); cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT).should('be.visible'); openJsonTab(); + // the json component is rendered within a dom element with overflow, so Cypress isn't finding it + // this next line is a hack that vertically scrolls down to ensure Cypress finds it + scrollWithinDocumentDetailsExpandableFlyoutRightSection(0, 6500); cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT).should('be.visible'); }); }); diff --git a/x-pack/plugins/security_solution/cypress/helpers/common.ts b/x-pack/plugins/security_solution/cypress/helpers/common.ts index 16728fc585234..1a9f91a9aa219 100644 --- a/x-pack/plugins/security_solution/cypress/helpers/common.ts +++ b/x-pack/plugins/security_solution/cypress/helpers/common.ts @@ -16,4 +16,4 @@ export const getDataTestSubjectSelector = (dataTestSubjectValue: string) => * Helper function to generate selector by class * @param className the value passed to class property of the DOM element */ -export const getClassSelector = (className: string) => `[.${className}]`; +export const getClassSelector = (className: string) => `.${className}`; 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 c05a8abfca337..e1f2588d38b9a 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 @@ -30,7 +30,6 @@ import { } from '../../public/flyout/right/test_ids'; import { JSON_TAB_CONTENT_TEST_ID, - OVERVIEW_TAB_CONTENT_TEST_ID, TABLE_TAB_CONTENT_TEST_ID, } from '../../public/flyout/right/tabs/test_ids'; import { @@ -42,6 +41,8 @@ import { } from '../../public/flyout/right/components/test_ids'; import { getDataTestSubjectSelector } from '../helpers/common'; +/* Right section */ + export const DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE = getDataTestSubjectSelector( FLYOUT_HEADER_TITLE_TEST_ID ); @@ -55,14 +56,13 @@ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB = getDataTestSubjectSelector(OVERVIEW_TAB_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB = getDataTestSubjectSelector(TABLE_TAB_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_JSON_TAB = getDataTestSubjectSelector(JSON_TAB_TEST_ID); -export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_CONTENT = getDataTestSubjectSelector( - OVERVIEW_TAB_CONTENT_TEST_ID -); export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT = getDataTestSubjectSelector(TABLE_TAB_CONTENT_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT = getDataTestSubjectSelector(JSON_TAB_CONTENT_TEST_ID); +/* Left section */ + export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB = getDataTestSubjectSelector(VISUALIZE_TAB_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB = diff --git a/x-pack/plugins/security_solution/cypress/tasks/document_expandable_flyout.ts b/x-pack/plugins/security_solution/cypress/tasks/document_expandable_flyout.ts index 2f0589d660bb0..7ff7edcda7ba4 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/document_expandable_flyout.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/document_expandable_flyout.ts @@ -19,6 +19,7 @@ import { DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON, } from '../screens/document_expandable_flyout'; import { EXPAND_ALERT_BTN } from '../screens/alerts'; +import { getClassSelector } from '../helpers/common'; /** * Find the first alert row in the alerts table then click on the expand icon button to open the flyout @@ -39,6 +40,13 @@ export const expandDocumentDetailsExpandableFlyoutLeftSection = () => export const collapseDocumentDetailsExpandableFlyoutLeftSection = () => cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON).should('be.visible').click(); +/** + * Scroll to x-y positions within the right section of the document details expandable flyout + * // TODO revisit this as it seems very fragile: the first element found is the timeline flyout, which isn't visible but still exist in the DOM + */ +export const scrollWithinDocumentDetailsExpandableFlyoutRightSection = (x: number, y: number) => + cy.get(getClassSelector('euiFlyout')).last().scrollTo(x, y); + /** * Open the Overview tab in the document details expandable flyout right section */ diff --git a/x-pack/plugins/security_solution/public/flyout/right/context.tsx b/x-pack/plugins/security_solution/public/flyout/right/context.tsx index f79173fd26583..e96a85156e6e2 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/context.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { BrowserFields, TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; import { css } from '@emotion/react'; import React, { createContext, useContext, useMemo } from 'react'; import type { SearchHit } from '@kbn/es-types'; @@ -27,6 +28,14 @@ export interface RightPanelContext { * Name of the index used in the parent's page */ indexName: string; + /** + * An object containing fields by type + */ + browserFields: BrowserFields | null; + /** + * An array of field objects with category and value + */ + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; /** * The actual raw document object */ @@ -51,7 +60,7 @@ export const RightPanelProvider = ({ id, indexName, children }: RightPanelProvid ? SourcererScopeName.detections : SourcererScopeName.default; const sourcererDataView = useSourcererDataView(sourcererScope); - const [loading, _, searchHit] = useTimelineEventsDetails({ + const [loading, dataFormattedForFieldBrowser, searchHit] = useTimelineEventsDetails({ indexName: eventIndex, eventId: id ?? '', runtimeMappings: sourcererDataView.runtimeMappings, @@ -64,10 +73,12 @@ export const RightPanelProvider = ({ id, indexName, children }: RightPanelProvid ? { eventId: id, indexName, + browserFields: sourcererDataView.browserFields, + dataFormattedForFieldBrowser, searchHit: searchHit as SearchHit, } : undefined, - [id, indexName, searchHit] + [id, indexName, sourcererDataView.browserFields, dataFormattedForFieldBrowser, searchHit] ); if (loading) { diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.stories.tsx new file mode 100644 index 0000000000000..7b75982a69063 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.stories.tsx @@ -0,0 +1,42 @@ +/* + * 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 { RightPanelContext } from '../context'; +import { JsonTab } from './json_tab'; + +export default { + component: JsonTab, + title: 'Flyout/JsonTab', +}; + +export const Default: Story = () => { + const contextValue = { + searchHit: { + some_field: 'some_value', + }, + } as unknown as RightPanelContext; + + return ( + + + + ); +}; + +export const Error: Story = () => { + const contextValue = { + searchHit: null, + } as unknown as RightPanelContext; + + return ( + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx new file mode 100644 index 0000000000000..f714a3c7839e5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.test.tsx @@ -0,0 +1,48 @@ +/* + * 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 { JsonTab } from './json_tab'; +import { JSON_TAB_ERROR_TEST_ID, JSON_TAB_CONTENT_TEST_ID } from './test_ids'; + +describe('', () => { + it('should render code block component', () => { + const contextValue = { + searchHit: { + some_field: 'some_value', + }, + } as unknown as RightPanelContext; + + const { getByTestId } = render( + + + + ); + + expect(getByTestId(JSON_TAB_CONTENT_TEST_ID)).toBeInTheDocument(); + }); + + it('should render error message on invalid searchHit', () => { + const contextValue = { + searchHit: null, + } as unknown as RightPanelContext; + + const { getByTestId, getByText } = render( + + + + ); + + expect(getByTestId(JSON_TAB_ERROR_TEST_ID)).toBeInTheDocument(); + expect(getByText('Unable to display document information')).toBeInTheDocument(); + expect( + getByText('There was an error displaying the document fields and values') + ).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx index edd6d318565d3..e4b974f6234fe 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/json_tab.tsx @@ -5,16 +5,33 @@ * 2.0. */ -import { EuiText } from '@elastic/eui'; +import { EuiEmptyPrompt } from '@elastic/eui'; import type { FC } from 'react'; import React, { memo } from 'react'; -import { JSON_TAB_CONTENT_TEST_ID } from './test_ids'; +import { JSON_TAB_ERROR_TEST_ID } from './test_ids'; +import { ERROR_MESSAGE, ERROR_TITLE } from './translations'; +import { JsonView } from '../../../common/components/event_details/json_view'; +import { useRightPanelContext } from '../context'; /** * Json view displayed in the document details expandable flyout right section */ export const JsonTab: FC = memo(() => { - return {'Json tab'}; + const { searchHit } = useRightPanelContext(); + + if (!searchHit) { + return ( + {ERROR_TITLE}} + body={

{ERROR_MESSAGE}

} + data-test-subj={JSON_TAB_ERROR_TEST_ID} + /> + ); + } + + return ; }); JsonTab.displayName = 'JsonTab'; 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 6431848df6bf7..c80371ba6b55a 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 @@ -8,4 +8,5 @@ export const OVERVIEW_TAB_CONTENT_TEST_ID = 'securitySolutionDocumentDetailsFlyoutOverviewTabContent'; export const TABLE_TAB_CONTENT_TEST_ID = 'securitySolutionDocumentDetailsFlyoutTableTabContent'; -export const JSON_TAB_CONTENT_TEST_ID = 'securitySolutionDocumentDetailsFlyoutJsonTabContent'; +export const JSON_TAB_CONTENT_TEST_ID = 'jsonView'; +export const JSON_TAB_ERROR_TEST_ID = 'securitySolutionDocumentDetailsFlyoutJsonTabError'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/tabs/translations.ts b/x-pack/plugins/security_solution/public/flyout/right/tabs/translations.ts new file mode 100644 index 0000000000000..0f0a4aef48dc0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/tabs/translations.ts @@ -0,0 +1,20 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const ERROR_TITLE = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.errorTitle', + { + defaultMessage: 'Unable to display document information', + } +); + +export const ERROR_MESSAGE = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.errorMessage', + { defaultMessage: 'There was an error displaying the document fields and values' } +);