From 6d4b37ea19f00fe0a746384be0423b201e68253b Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 23 Apr 2024 14:58:00 +0100 Subject: [PATCH 01/30] Retrieve onboarding steps --- .../guided_onboarding/siem_guide_config.ts | 2 +- .../guided_onboarding_tour/tour_config.ts | 2 +- .../right/components/tour.tsx | 10 ++- .../document_details/right/header.test.tsx | 77 +++++++++++++++++++ .../flyout/document_details/right/header.tsx | 26 ++++++- 5 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/right/header.test.tsx diff --git a/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts b/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts index 9ee5775d06d62..8a3adaf376d98 100644 --- a/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts +++ b/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts @@ -45,7 +45,7 @@ export const getSiemGuideConfig = (launchDarkly = defaultGuideTranslations): Gui telemetryId: siemGuideId, completedGuideRedirectLocation: { appID: 'securitySolutionUI', - path: '/dashboards', + path: '/alerts', }, description: launchDarkly.description ?? defaultGuideTranslations.description, docs: { diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts index ca52149bfc329..dcdf171abe022 100644 --- a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts +++ b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts @@ -112,7 +112,7 @@ const alertsCasesConfig: StepConfig[] = [ anchor: `[tour-step="${getTourAnchor( AlertsCasesTourSteps.reviewAlertDetailsFlyout, SecurityStepId.alertsCases - )}"] span.euiTab__content`, + )}"]`, offset: 20, anchorPosition: 'leftUp', dataTestSubj: getTourAnchor( diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/tour.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/tour.tsx index 423619d59f13a..f7177a20026d3 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/tour.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/tour.tsx @@ -19,18 +19,26 @@ import { DocumentDetailsRightPanelKey } from '..'; import { DocumentDetailsLeftPanelKey } from '../../left'; import { EventKind } from '../../shared/constants/event_kinds'; import { useIsTimelineFlyoutOpen } from '../../shared/hooks/use_is_timeline_flyout_open'; +import { useTourContext } from '../../../../common/components/guided_onboarding_tour/tour'; +import { SecurityStepId } from '../../../../common/components/guided_onboarding_tour/tour_config'; /** * Guided tour for the right panel in details flyout */ export const RightPanelTour: FC = memo(() => { + const { isTourShown: isGuidedOnboardingTourShown } = useTourContext(); + const { openLeftPanel, openRightPanel } = useExpandableFlyoutApi(); const { eventId, indexName, scopeId, isPreview, getFieldsData } = useRightPanelContext(); const eventKind = getField(getFieldsData('event.kind')); const isAlert = eventKind === EventKind.signal; const isTimelineFlyoutOpen = useIsTimelineFlyoutOpen(); - const showTour = isAlert && !isPreview && !isTimelineFlyoutOpen; + const showTour = + isAlert && + !isPreview && + !isTimelineFlyoutOpen && + !isGuidedOnboardingTourShown(SecurityStepId.alertsCases); const goToLeftPanel = useCallback(() => { openLeftPanel({ diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/header.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/header.test.tsx new file mode 100644 index 0000000000000..b984b007258fe --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/header.test.tsx @@ -0,0 +1,77 @@ +/* + * 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 { PanelHeader } from './header'; +import { allThreeTabs } from './hooks/use_tabs'; +import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step'; +import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; + +jest.mock('./context', () => ({ + useRightPanelContext: jest.fn().mockReturnValue({ dataFormattedForFieldBrowser: [] }), +})); +jest.mock('../../../timelines/components/side_panel/event_details/helpers', () => ({ + useBasicDataFromDetailsData: jest.fn(), +})); +jest.mock('../../../common/components/guided_onboarding_tour/tour_step', () => ({ + GuidedOnboardingTourStep: jest.fn().mockReturnValue(
), +})); + +jest.mock('./components/alert_header_title', () => ({ + AlertHeaderTitle: jest.fn().mockReturnValue(
), +})); + +jest.mock('./components/event_header_title', () => ({ + EventHeaderTitle: jest.fn().mockReturnValue(
), +})); + +const mockUseBasicDataFromDetailsData = useBasicDataFromDetailsData as jest.Mock; +const mockGuidedOnboardingTourStep = GuidedOnboardingTourStep as jest.Mock; + +describe('PanelHeader', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render tab name', () => { + mockUseBasicDataFromDetailsData.mockReturnValue({ isAlert: false }); + const { getByText } = render( + + ); + expect(GuidedOnboardingTourStep).not.toBeCalled(); + expect(getByText('Overview')).toBeInTheDocument(); + }); + + it('should render event header title when isAlert equals false', () => { + mockUseBasicDataFromDetailsData.mockReturnValue({ isAlert: false }); + const { queryByTestId } = render( + + ); + expect(queryByTestId('alert-header')).not.toBeInTheDocument(); + expect(queryByTestId('event-header')).toBeInTheDocument(); + }); + + it('should render alert header title when isAlert equals true', () => { + mockUseBasicDataFromDetailsData.mockReturnValue({ isAlert: true }); + const { queryByTestId } = render( + + ); + expect(queryByTestId('alert-header')).toBeInTheDocument(); + expect(queryByTestId('event-header')).not.toBeInTheDocument(); + }); + + it('should render tab name with guided onboarding tour info', () => { + mockUseBasicDataFromDetailsData.mockReturnValue({ isAlert: true }); + render( + + ); + expect(mockGuidedOnboardingTourStep.mock.calls[0][0].isTourAnchor).toBe(true); + expect(mockGuidedOnboardingTourStep.mock.calls[0][0].step).toBe(3); + expect(mockGuidedOnboardingTourStep.mock.calls[0][0].tourId).toBe('alertsCases'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx index 70c27e18faa23..4f8713ed3e6ec 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx @@ -7,7 +7,7 @@ import { EuiSpacer, EuiTab } from '@elastic/eui'; import type { FC } from 'react'; -import React, { memo } from 'react'; +import React, { memo, useMemo } from 'react'; import type { RightPanelPaths } from '.'; import type { RightPanelTabType } from './tabs'; import { FlyoutHeader } from '../../shared/components/flyout_header'; @@ -16,6 +16,12 @@ import { AlertHeaderTitle } from './components/alert_header_title'; import { EventHeaderTitle } from './components/event_header_title'; import { useRightPanelContext } from './context'; import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; +import { + AlertsCasesTourSteps, + getTourAnchor, + SecurityStepId, +} from '../../../common/components/guided_onboarding_tour/tour_config'; +import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step'; export interface PanelHeaderProps { /** @@ -38,6 +44,12 @@ export const PanelHeader: FC = memo( const { dataFormattedForFieldBrowser } = useRightPanelContext(); const { isAlert } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); const onSelectedTabChanged = (id: RightPanelPaths) => setSelectedTabId(id); + + const tourAnchor = useMemo( + () => (isAlert ? { 'tour-step': getTourAnchor(3, SecurityStepId.alertsCases) } : {}), + [isAlert] + ); + const renderTabs = tabs.map((tab, index) => ( onSelectedTabChanged(tab.id)} @@ -45,7 +57,17 @@ export const PanelHeader: FC = memo( key={index} data-test-subj={tab['data-test-subj']} > - {tab.name} + {isAlert ? ( + + {tab.name} + + ) : ( + tab.name + )} )); From 736605243262795781bc7c7f4326c86d40d7a568 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 23 Apr 2024 17:50:47 +0100 Subject: [PATCH 02/30] compatible with legacy flyout --- .../guided_onboarding_tour/tour_config.ts | 2 +- .../flyout/document_details/right/header.tsx | 56 ++++++++++++------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts index dcdf171abe022..ca52149bfc329 100644 --- a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts +++ b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts @@ -112,7 +112,7 @@ const alertsCasesConfig: StepConfig[] = [ anchor: `[tour-step="${getTourAnchor( AlertsCasesTourSteps.reviewAlertDetailsFlyout, SecurityStepId.alertsCases - )}"]`, + )}"] span.euiTab__content`, offset: 20, anchorPosition: 'leftUp', dataTestSubj: getTourAnchor( diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx index 4f8713ed3e6ec..1d3a9938336ee 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/header.tsx @@ -46,30 +46,46 @@ export const PanelHeader: FC = memo( const onSelectedTabChanged = (id: RightPanelPaths) => setSelectedTabId(id); const tourAnchor = useMemo( - () => (isAlert ? { 'tour-step': getTourAnchor(3, SecurityStepId.alertsCases) } : {}), + () => + isAlert + ? { + 'tour-step': getTourAnchor( + AlertsCasesTourSteps.reviewAlertDetailsFlyout, + SecurityStepId.alertsCases + ), + } + : {}, [isAlert] ); - const renderTabs = tabs.map((tab, index) => ( - onSelectedTabChanged(tab.id)} - isSelected={tab.id === selectedTabId} - key={index} - data-test-subj={tab['data-test-subj']} - > - {isAlert ? ( - + isAlert ? ( + + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + key={index} + data-test-subj={tab['data-test-subj']} + {...tourAnchor} > - {tab.name} - - ) : ( - tab.name - )} - - )); + {tab.name} + + + ) : ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + key={index} + data-test-subj={tab['data-test-subj']} + > + {tab.name} + + ) + ); return ( From 6e15bde4f7d21ebaf8ca66a83f9460d02c512f90 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 1 May 2024 22:49:49 +0100 Subject: [PATCH 03/30] hide onboarding steps when cases opened --- .../use_add_to_existing_case_modal_open.tsx | 15 ++ .../public/components/cases_context/index.tsx | 12 + .../use_is_add_to_new_case_flyout_open.tsx | 15 ++ x-pack/plugins/cases/public/plugin.ts | 4 + x-pack/plugins/cases/public/types.ts | 4 + .../guided_onboarding/siem_guide_config.ts | 2 +- .../control_columns/row_action/index.tsx | 79 +++++-- .../event_details/event_details.tsx | 207 +++++++++--------- .../guided_onboarding_tour/tour_step.tsx | 41 +++- .../components/header_actions/actions.tsx | 18 +- .../render_cell_value.tsx | 41 +++- .../left/components/related_cases.tsx | 22 +- .../right/components/insights_section.tsx | 12 +- .../right/components/tour.tsx | 10 +- .../flyout/document_details/right/header.tsx | 2 +- 15 files changed, 337 insertions(+), 147 deletions(-) create mode 100644 x-pack/plugins/cases/public/components/all_cases/selector_modal/use_add_to_existing_case_modal_open.tsx create mode 100644 x-pack/plugins/cases/public/components/create/flyout/use_is_add_to_new_case_flyout_open.tsx diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_add_to_existing_case_modal_open.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_add_to_existing_case_modal_open.tsx new file mode 100644 index 0000000000000..6467182285e3e --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_add_to_existing_case_modal_open.tsx @@ -0,0 +1,15 @@ +/* + * 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 { useCasesContext } from '../../cases_context/use_cases_context'; + +export const useAddToExistingCaseModalOpen = () => { + const { isSelectCaseModalOpen } = useCasesContext(); + return isSelectCaseModalOpen; +}; + +export type UseAddToExistingCaseModalOpen = typeof useAddToExistingCaseModalOpen; diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index a59a76f8adb6a..e39a5296b548d 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -44,6 +44,8 @@ export interface CasesContextValue { features: CasesFeaturesAllRequired; releasePhase: ReleasePhase; dispatch: CasesContextValueDispatch; + isCreateCaseFlyoutOpen: boolean; + isSelectCaseModalOpen: boolean; } export interface CasesContextProps @@ -83,6 +85,12 @@ export const CasesProvider: FC< }) => { const [state, dispatch] = useReducer(casesContextReducer, getInitialCasesContextState()); + console.log( + '-----cases---', + state.createCaseFlyout.isFlyoutOpen, + state.selectCaseModal.isModalOpen + ); + const value: CasesContextValue = useMemo( () => ({ externalReferenceAttachmentTypeRegistry, @@ -110,6 +118,8 @@ export const CasesProvider: FC< ), releasePhase, dispatch, + isCreateCaseFlyoutOpen: state.createCaseFlyout.isFlyoutOpen, + isSelectCaseModalOpen: state.selectCaseModal.isModalOpen, }), /** * We want to trigger a rerender only when the permissions will change. @@ -126,6 +136,8 @@ export const CasesProvider: FC< permissions.read, permissions.settings, permissions.update, + state.createCaseFlyout.isFlyoutOpen, + state.selectCaseModal.isModalOpen, ] ); diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_is_add_to_new_case_flyout_open.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_is_add_to_new_case_flyout_open.tsx new file mode 100644 index 0000000000000..31a799152462a --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/flyout/use_is_add_to_new_case_flyout_open.tsx @@ -0,0 +1,15 @@ +/* + * 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 { useCasesContext } from '../../cases_context/use_cases_context'; + +export const useIsAddToNewCaseFlyoutOpen = () => { + const { isCreateCaseFlyoutOpen } = useCasesContext(); + return isCreateCaseFlyoutOpen; +}; + +export type UseIsAddToNewCaseFlyoutOpen = typeof useIsAddToNewCaseFlyoutOpen; diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index 44393473767e6..e07109b48ad97 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -37,6 +37,8 @@ import type { CasesPublicStartDependencies, } from './types'; import { registerSystemActions } from './components/system_actions'; +import { useIsAddToNewCaseFlyoutOpen } from './components/create/flyout/use_is_add_to_new_case_flyout_open'; +import { useAddToExistingCaseModalOpen } from './components/all_cases/selector_modal/use_add_to_existing_case_modal_open'; /** * @public @@ -190,6 +192,8 @@ export class CasesUiPlugin hooks: { useCasesAddToNewCaseFlyout, useCasesAddToExistingCaseModal, + useIsAddToNewCaseFlyoutOpen, + useAddToExistingCaseModalOpen, }, helpers: { canUseCases: canUseCases(core.application.capabilities), diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts index bb8790a299d12..1ca4616644d39 100644 --- a/x-pack/plugins/cases/public/types.ts +++ b/x-pack/plugins/cases/public/types.ts @@ -60,6 +60,8 @@ import type { ExternalReferenceNoSOAttachmentPayload, ExternalReferenceSOAttachmentPayload, } from '../common/types/domain'; +import type { UseIsAddToNewCaseFlyoutOpen } from './components/create/flyout/use_is_add_to_new_case_flyout_open'; +import type { UseAddToExistingCaseModalOpen } from './components/all_cases/selector_modal/use_add_to_existing_case_modal_open'; export interface CasesPublicSetupDependencies { files: FilesSetup; @@ -154,6 +156,8 @@ export interface CasesPublicStart { hooks: { useCasesAddToNewCaseFlyout: UseCasesAddToNewCaseFlyout; useCasesAddToExistingCaseModal: UseCasesAddToExistingCaseModal; + useIsAddToNewCaseFlyoutOpen: UseIsAddToNewCaseFlyoutOpen; + useAddToExistingCaseModalOpen: UseAddToExistingCaseModalOpen; }; helpers: { /** diff --git a/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts b/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts index 8a3adaf376d98..9ee5775d06d62 100644 --- a/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts +++ b/x-pack/plugins/security_solution/common/guided_onboarding/siem_guide_config.ts @@ -45,7 +45,7 @@ export const getSiemGuideConfig = (launchDarkly = defaultGuideTranslations): Gui telemetryId: siemGuideId, completedGuideRedirectLocation: { appID: 'securitySolutionUI', - path: '/alerts', + path: '/dashboards', }, description: launchDarkly.description ?? defaultGuideTranslations.description, docs: { diff --git a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx index a9810a03960fd..f0eec4a69e681 100644 --- a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx @@ -14,7 +14,10 @@ import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; import { useKibana } from '../../../lib/kibana'; import { timelineActions } from '../../../../timelines/store'; import { ENABLE_EXPANDABLE_FLYOUT_SETTING } from '../../../../../common/constants'; -import { DocumentDetailsRightPanelKey } from '../../../../flyout/document_details/shared/constants/panel_keys'; +import { + DocumentDetailsLeftPanelKey, + DocumentDetailsRightPanelKey, +} from '../../../../flyout/document_details/shared/constants/panel_keys'; import type { SetEventsDeleted, SetEventsLoading, @@ -26,6 +29,10 @@ import type { TimelineItem, TimelineNonEcsData } from '../../../../../common/sea import type { ColumnHeaderOptions, OnRowSelected } from '../../../../../common/types/timeline'; import { TimelineId } from '../../../../../common/types'; import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; +import { useTourContext } from '../../guided_onboarding_tour'; +import { SecurityStepId } from '../../guided_onboarding_tour/tour_config'; +import { LeftPanelInsightsTab } from '../../../../flyout/document_details/left'; +import { CORRELATIONS_TAB_ID } from '../../../../flyout/document_details/left/components/correlations_details'; type Props = EuiDataGridCellValueElementProps & { columnHeaders: ColumnHeaderOptions[]; @@ -78,6 +85,7 @@ const RowActionComponent = ({ const isExpandableFlyoutInCreateRuleEnabled = useIsExperimentalFeatureEnabled( 'expandableFlyoutInCreateRuleEnabled' ); + const { isTourShown: isGuidedOnboardingTourShown } = useTourContext(); const columnValues = useMemo( () => @@ -111,20 +119,51 @@ const RowActionComponent = ({ }; if (showExpandableFlyout) { - openFlyout({ - right: { - id: DocumentDetailsRightPanelKey, - params: { - id: eventId, - indexName, - scopeId: tableId, + // If Guided onboarding is enabled, open the flyout with the left panel strght away + // As we want to show the case links in the correlation tab + if (isGuidedOnboardingTourShown(SecurityStepId.alertsCases)) { + openFlyout({ + right: { + id: DocumentDetailsRightPanelKey, + params: { + id: eventId, + indexName, + scopeId: tableId, + }, }, - }, - }); - telemetry.reportDetailsFlyoutOpened({ - location: tableId, - panel: 'right', - }); + left: { + id: DocumentDetailsLeftPanelKey, + path: { + tab: LeftPanelInsightsTab, + subTab: CORRELATIONS_TAB_ID, + }, + params: { + id: eventId, + indexName, + scopeId: tableId, + }, + }, + }); + telemetry.reportDetailsFlyoutOpened({ + location: tableId, + panel: 'left', + }); + } else { + openFlyout({ + right: { + id: DocumentDetailsRightPanelKey, + params: { + id: eventId, + indexName, + scopeId: tableId, + }, + }, + }); + telemetry.reportDetailsFlyoutOpened({ + location: tableId, + panel: 'right', + }); + } } // TODO remove when https://github.com/elastic/security-team/issues/7462 is merged // support of old flyout in cases page @@ -147,7 +186,17 @@ const RowActionComponent = ({ }) ); } - }, [dispatch, eventId, indexName, openFlyout, tabType, tableId, showExpandableFlyout, telemetry]); + }, [ + eventId, + indexName, + showExpandableFlyout, + tableId, + isGuidedOnboardingTourShown, + openFlyout, + telemetry, + dispatch, + tabType, + ]); const Action = controlColumn.rowCellRender; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 6c1dd9bce910a..70d0c29eeda4b 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -231,104 +231,111 @@ const EventDetailsComponent: React.FC = ({ name: i18n.OVERVIEW, 'data-test-subj': 'overviewTab', content: ( - <> - - - - {threatDetails && threatDetails[0] && ( - - <> - -
{threatDetails[0].title}
-
- - {threatDetails[0].description} - - -
- )} - - {renderer != null && detailsEcsData != null && ( -
- -
{i18n.ALERT_REASON}
-
- - - {renderer.renderRow({ - contextId: EVENT_DETAILS_CONTEXT_ID, - data: detailsEcsData, - isDraggable: isDraggable ?? false, - scopeId, - })} - -
- )} - - - - - - {showThreatSummary && ( - + <> + + + + {threatDetails && threatDetails[0] && ( + + <> + +
{threatDetails[0].title}
+
+ + {threatDetails[0].description} + + +
+ )} + + {renderer != null && detailsEcsData != null && ( +
+ +
{i18n.ALERT_REASON}
+
+ + + {renderer.renderRow({ + contextId: EVENT_DETAILS_CONTEXT_ID, + data: detailsEcsData, + isDraggable: isDraggable ?? false, + scopeId, + })} + +
+ )} + + + + - )} - {isEnrichmentsLoading && ( - <> - - - )} + {showThreatSummary && ( + + )} + + {isEnrichmentsLoading && ( + <> + + + )} - {basicAlertData.ruleId && maybeRule?.note && ( - - )} - + {basicAlertData.ruleId && maybeRule?.note && ( + + )} + + ), } : undefined, [ isAlert, + isTourAnchor, browserFields, scopeId, data, @@ -340,7 +347,7 @@ const EventDetailsComponent: React.FC = ({ detailsEcsData, isDraggable, goToTableTab, - maybeRule?.investigation_fields, + maybeRule?.investigation_fields?.field_names, maybeRule?.note, showThreatSummary, hostRisk, @@ -469,23 +476,17 @@ const EventDetailsComponent: React.FC = ({ ); return ( - - <> - - - - + <> + + + ); }; EventDetailsComponent.displayName = 'EventDetailsComponent'; diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.tsx b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.tsx index 156604160be74..90fd5b5b9a996 100644 --- a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.tsx +++ b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.tsx @@ -18,6 +18,7 @@ import { timelineDefaults } from '../../../timelines/store/defaults'; import { timelineSelectors } from '../../../timelines/store'; import { useTourContext } from './tour'; import { AlertsCasesTourSteps, SecurityStepId, securityTourConfig } from './tour_config'; +import { useKibana } from '../../lib/kibana'; interface SecurityTourStep { children?: React.ReactElement; @@ -140,8 +141,27 @@ interface GuidedOnboardingTourStep extends SecurityTourStep { // can be false if the anchor is an iterative element // do not use this as an "is tour active" check, the SecurityTourStep checks that anyway isTourAnchor?: boolean; + + hidden?: boolean; } +export const hiddenWhenExpandableFlyoutExpanded: Record = { + [SecurityStepId.alertsCases]: [ + AlertsCasesTourSteps.pointToAlertName, + AlertsCasesTourSteps.expandEvent, + ], +}; + +export const hiddenWhenCaseFlyoutExpanded: Record = { + [SecurityStepId.alertsCases]: [ + AlertsCasesTourSteps.pointToAlertName, + AlertsCasesTourSteps.expandEvent, + AlertsCasesTourSteps.reviewAlertDetailsFlyout, + AlertsCasesTourSteps.addAlertToCase, + AlertsCasesTourSteps.viewCase, + ], +}; + // wraps tour anchor component // and gives the tour step itself a place to mount once it is active // mounts the tour step with a delay to ensure the anchor will render first @@ -150,6 +170,23 @@ export const GuidedOnboardingTourStep = ({ // can be false if the anchor is an iterative element // do not use this as an "is tour active" check, the SecurityTourStep checks that anyway isTourAnchor = true, + hidden = false, ...props -}: GuidedOnboardingTourStep) => - isTourAnchor ? {children} : <>{children}; +}: GuidedOnboardingTourStep) => { + const { cases } = useKibana().services; + const isAddToNewCaseFlyoutOpen = cases.hooks.useIsAddToNewCaseFlyoutOpen(); + const isAddToExistingCaseModalOpen = cases.hooks.useAddToExistingCaseModalOpen(); + + const hiddenWhenCasesModalFlyoutExpanded = useMemo( + () => + (isAddToNewCaseFlyoutOpen || isAddToExistingCaseModalOpen) && + hiddenWhenCaseFlyoutExpanded[props.tourId]?.includes(props.step), + [isAddToNewCaseFlyoutOpen, isAddToExistingCaseModalOpen, props.tourId, props.step] + ); + + return isTourAnchor && !hidden && !hiddenWhenCasesModalFlyoutExpanded ? ( + {children} + ) : ( + <>{children} + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx index 7db76c66e53df..9877297ec7fbc 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx @@ -11,6 +11,7 @@ import { EuiButtonIcon, EuiCheckbox, EuiLoadingSpinner, EuiToolTip } from '@elas import styled from 'styled-components'; import { TimelineTabs, TableId } from '@kbn/securitysolution-data-table'; +import { useExpandableFlyoutState } from '@kbn/expandable-flyout'; import { eventHasNotes, getEventType, @@ -38,7 +39,10 @@ import * as i18n from './translations'; import { useTourContext } from '../guided_onboarding_tour'; import { AlertsCasesTourSteps, SecurityStepId } from '../guided_onboarding_tour/tour_config'; import { isDetectionsAlertsTable } from '../top_n/helpers'; -import { GuidedOnboardingTourStep } from '../guided_onboarding_tour/tour_step'; +import { + GuidedOnboardingTourStep, + hiddenWhenCaseFlyoutExpanded, +} from '../guided_onboarding_tour/tour_step'; import { DEFAULT_ACTION_BUTTON_WIDTH, isAlert } from './helpers'; const ActionsContainer = styled.div` @@ -78,6 +82,17 @@ const ActionsComponent: React.FC = ({ const isEnterprisePlus = useLicense().isEnterprise(); + const panels = useExpandableFlyoutState(); + const isExpandableFlyoutExpanded: boolean = !!panels.right; + + const hiddenWhenExpandableFlyoutOpened = useMemo( + () => + isExpandableFlyoutExpanded && + hiddenWhenCaseFlyoutExpanded[SecurityStepId.alertsCases]?.includes( + AlertsCasesTourSteps.expandEvent + ), + [isExpandableFlyoutExpanded] + ); const onPinEvent: OnPinEvent = useCallback( (evtId) => dispatch(timelineActions.pinEvent({ id: timelineId, eventId: evtId })), [dispatch, timelineId] @@ -249,6 +264,7 @@ const ActionsComponent: React.FC = ({ onClick={onExpandEvent} step={AlertsCasesTourSteps.expandEvent} tourId={SecurityStepId.alertsCases} + hidden={hiddenWhenExpandableFlyoutOpened} >
diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx index 413e9beba6015..5b97f170a58a3 100644 --- a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx @@ -12,11 +12,15 @@ import { find, getOr } from 'lodash/fp'; import type { TimelineNonEcsData } from '@kbn/timelines-plugin/common'; import { tableDefaults, dataTableSelectors } from '@kbn/securitysolution-data-table'; import type { TableId } from '@kbn/securitysolution-data-table'; +import { useExpandableFlyoutState } from '@kbn/expandable-flyout'; import { useLicense } from '../../../common/hooks/use_license'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; import type { SourcererScopeName } from '../../../common/store/sourcerer/model'; -import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step'; +import { + GuidedOnboardingTourStep, + hiddenWhenCaseFlyoutExpanded, +} from '../../../common/components/guided_onboarding_tour/tour_step'; import { isDetectionsAlertsTable } from '../../../common/components/top_n/helpers'; import { AlertsCasesTourSteps, @@ -116,6 +120,17 @@ export const RenderCellValue: React.FC + isExpandableFlyoutExpanded && + hiddenWhenCaseFlyoutExpanded[SecurityStepId.alertsCases]?.includes( + AlertsCasesTourSteps.pointToAlertName + ), + [isExpandableFlyoutExpanded] + ); const Renderer = useMemo(() => { const myHeader = header ?? { id: columnId, ...browserFieldsByName[columnId] }; @@ -126,6 +141,7 @@ export const RenderCellValue: React.FC