From f38a37c0c175a7f25c7dd8474de1cb732c1da09e Mon Sep 17 00:00:00 2001 From: Philippe Oberti Date: Wed, 16 Aug 2023 13:01:59 +0200 Subject: [PATCH] [Security Solution] expandable flyout - expandable mode only for signal documents (#163557) --- .../right/components/header_title.test.tsx | 4 +- .../flyout/right/components/header_title.tsx | 13 +++- .../public/flyout/right/content.tsx | 11 +++- .../public/flyout/right/header.test.tsx | 60 +++++++++++++++++++ .../public/flyout/right/header.tsx | 44 +++++++++----- .../public/flyout/right/index.tsx | 23 +++++-- 6 files changed, 127 insertions(+), 28 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/flyout/right/header.test.tsx diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/header_title.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/header_title.test.tsx index 010535ffc55c7..f4c24e64954ba 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/header_title.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/header_title.test.tsx @@ -39,7 +39,7 @@ const renderHeader = (contextValue: RightPanelContext) => - + @@ -52,7 +52,7 @@ describe('', () => { jest.mocked(useAssistant).mockReturnValue({ showAssistant: true, promptContextId: '' }); }); - it('should render mitre attack information', () => { + it('should render component', () => { const contextValue = { dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, getFieldsData: jest.fn().mockImplementation(mockGetFieldsData), diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/header_title.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/header_title.tsx index 23bf91053220a..eca086385eb17 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/header_title.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/header_title.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { FC } from 'react'; +import type { VFC } from 'react'; import React, { memo } from 'react'; import { NewChatById } from '@kbn/elastic-assistant'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; @@ -26,10 +26,17 @@ import { PreferenceFormattedDate } from '../../../common/components/formatted_da import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids'; import { ShareButton } from './share_button'; +export interface HeaderTitleProps { + /** + * If false, update the margin-top to compensate the fact that the expand detail button is not displayed + */ + flyoutIsExpandable: boolean; +} + /** * Document details flyout right section header */ -export const HeaderTitle: FC = memo(() => { +export const HeaderTitle: VFC = memo(({ flyoutIsExpandable }) => { const { dataFormattedForFieldBrowser } = useRightPanelContext(); const { isAlert, ruleName, timestamp, alertUrl } = useBasicDataFromDetailsData( dataFormattedForFieldBrowser @@ -48,7 +55,7 @@ export const HeaderTitle: FC = memo(() => { justifyContent="flexEnd" gutterSize="none" css={css` - margin-top: -44px; + margin-top: ${flyoutIsExpandable ? '-44px' : '-28px'}; padding: 0 25px; `} > diff --git a/x-pack/plugins/security_solution/public/flyout/right/content.tsx b/x-pack/plugins/security_solution/public/flyout/right/content.tsx index 9dd8391d24d11..d0d0b0a3b80b9 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/content.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/content.tsx @@ -10,23 +10,28 @@ import type { VFC } from 'react'; import React, { useMemo } from 'react'; import { FLYOUT_BODY_TEST_ID } from './test_ids'; import type { RightPanelPaths } from '.'; -import { tabs } from './tabs'; +import type { RightPanelTabsType } from './tabs'; +import {} from './tabs'; export interface PanelContentProps { /** * Id of the tab selected in the parent component to display its content */ selectedTabId: RightPanelPaths; + /** + * Tabs display right below the flyout's header + */ + tabs: RightPanelTabsType; } /** * Document details expandable flyout right section, that will display the content * of the overview, table and json tabs. */ -export const PanelContent: VFC = ({ selectedTabId }) => { +export const PanelContent: VFC = ({ selectedTabId, tabs }) => { const selectedTabContent = useMemo(() => { return tabs.find((tab) => tab.id === selectedTabId)?.content; - }, [selectedTabId]); + }, [selectedTabId, tabs]); return {selectedTabContent}; }; diff --git a/x-pack/plugins/security_solution/public/flyout/right/header.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/header.test.tsx new file mode 100644 index 0000000000000..6432cbc2d41b6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/right/header.test.tsx @@ -0,0 +1,60 @@ +/* + * 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 { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { TestProviders } from '../../common/mock'; +import { RightPanelContext } from './context'; +import { mockContextValue } from './mocks/mock_right_panel_context'; +import { PanelHeader } from './header'; +import { + COLLAPSE_DETAILS_BUTTON_TEST_ID, + EXPAND_DETAILS_BUTTON_TEST_ID, +} from './components/test_ids'; +import { mockFlyoutContextValue } from '../shared/mocks/mock_flyout_context'; + +describe('', () => { + it('should render expand details button if flyout is expandable', () => { + const { getByTestId } = render( + + + + window.alert('test')} + tabs={[]} + /> + + + + ); + + expect(getByTestId(EXPAND_DETAILS_BUTTON_TEST_ID)).toBeInTheDocument(); + }); + + it('should not render expand details button if flyout is not expandable', () => { + const { queryByTestId } = render( + + + + window.alert('test')} + tabs={[]} + /> + + + + ); + + expect(queryByTestId(EXPAND_DETAILS_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(COLLAPSE_DETAILS_BUTTON_TEST_ID)).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/right/header.tsx b/x-pack/plugins/security_solution/public/flyout/right/header.tsx index 4f316b9a8be50..67425b6eb3565 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/header.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/header.tsx @@ -10,18 +10,32 @@ import type { VFC } from 'react'; import React, { memo } from 'react'; import { css } from '@emotion/react'; import type { RightPanelPaths } from '.'; -import { tabs } from './tabs'; +import type { RightPanelTabsType } from './tabs'; import { HeaderTitle } from './components/header_title'; import { ExpandDetailButton } from './components/expand_detail_button'; export interface PanelHeaderProps { + /** + * Id of the tab selected in the parent component to display its content + */ selectedTabId: RightPanelPaths; + /** + * Callback to set the selected tab id in the parent component + * @param selected + */ setSelectedTabId: (selected: RightPanelPaths) => void; - handleOnEventClosed?: () => void; + /** + * Tabs to display in the header + */ + tabs: RightPanelTabsType; + /** + * If true, the expand detail button will be displayed + */ + flyoutIsExpandable: boolean; } export const PanelHeader: VFC = memo( - ({ selectedTabId, setSelectedTabId, handleOnEventClosed }) => { + ({ flyoutIsExpandable, selectedTabId, setSelectedTabId, tabs }) => { const onSelectedTabChanged = (id: RightPanelPaths) => setSelectedTabId(id); const renderTabs = tabs.map((tab, index) => ( = memo( -
- -
+ {flyoutIsExpandable && ( +
+ +
+ )} - + > = memo(({ path }) => { const { openRightPanel } = useExpandableFlyoutContext(); - const { eventId, indexName, scopeId } = useRightPanelContext(); + const { eventId, getFieldsData, indexName, scopeId } = useRightPanelContext(); + + // for 8.10, we only render the flyout in its expandable mode if the document viewed is of type signal + const documentIsSignal = getField(getFieldsData('event.kind')) === EventKind.signal; + const tabsDisplayed = documentIsSignal ? tabs : tabs.filter((tab) => tab.id !== 'overview'); const selectedTabId = useMemo(() => { - const defaultTab = tabs[0].id; + const defaultTab = tabsDisplayed[0].id; if (!path) return defaultTab; - return tabs.map((tab) => tab.id).find((tabId) => tabId === path.tab) ?? defaultTab; - }, [path]); + return tabsDisplayed.map((tab) => tab.id).find((tabId) => tabId === path.tab) ?? defaultTab; + }, [path, tabsDisplayed]); const setSelectedTabId = (tabId: RightPanelTabsType[number]['id']) => { openRightPanel({ @@ -58,8 +64,13 @@ export const RightPanel: FC> = memo(({ path }) => { return ( <> - - + + );