Skip to content

Commit

Permalink
[Security Solution][Expandable flyout] - add onClose callback logic t…
Browse files Browse the repository at this point in the history
…o Security Solution application (elastic#188196)
  • Loading branch information
PhilippeOberti authored Jul 19, 2024
1 parent 6e73444 commit 09d7cfd
Show file tree
Hide file tree
Showing 18 changed files with 364 additions and 223 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import {
ATTACH_TO_TIMELINE_CHECKBOX_TEST_ID,
} from './test_ids';
import { ReqStatus } from '../../../../notes/store/notes.slice';
import { useIsTimelineFlyoutOpen } from '../../shared/hooks/use_is_timeline_flyout_open';
import { TimelineId } from '../../../../../common/types';
import userEvent from '@testing-library/user-event';
import { useWhichFlyout } from '../../shared/hooks/use_which_flyout';
import { Flyouts } from '../../shared/constants/flyouts';

jest.mock('../../shared/hooks/use_is_timeline_flyout_open');
jest.mock('../../shared/hooks/use_which_flyout');

const mockAddError = jest.fn();
jest.mock('../../../../common/hooks/use_app_toasts', () => ({
Expand Down Expand Up @@ -124,15 +125,15 @@ describe('AddNote', () => {
});

it('should disable attach to timeline checkbox if flyout is not open from timeline', () => {
(useIsTimelineFlyoutOpen as jest.Mock).mockReturnValue(false);
(useWhichFlyout as jest.Mock).mockReturnValue(Flyouts.securitySolution);

const { getByTestId } = renderAddNote();

expect(getByTestId(ATTACH_TO_TIMELINE_CHECKBOX_TEST_ID)).toHaveAttribute('disabled');
});

it('should disable attach to timeline checkbox if active timeline is not saved', () => {
(useIsTimelineFlyoutOpen as jest.Mock).mockReturnValue(true);
(useWhichFlyout as jest.Mock).mockReturnValue(Flyouts.timeline);

const store = createMockStore({
...mockGlobalState,
Expand All @@ -157,7 +158,7 @@ describe('AddNote', () => {
});

it('should have attach to timeline checkbox enabled', () => {
(useIsTimelineFlyoutOpen as jest.Mock).mockReturnValue(true);
(useWhichFlyout as jest.Mock).mockReturnValue(Flyouts.timeline);

const store = createMockStore({
...mockGlobalState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ import {
import { css } from '@emotion/react';
import { useDispatch, useSelector } from 'react-redux';
import { i18n } from '@kbn/i18n';
import { useWhichFlyout } from '../../shared/hooks/use_which_flyout';
import { Flyouts } from '../../shared/constants/flyouts';
import { useKibana } from '../../../../common/lib/kibana';
import { TimelineId } from '../../../../../common/types';
import { timelineSelectors } from '../../../../timelines/store';
import { useIsTimelineFlyoutOpen } from '../../shared/hooks/use_is_timeline_flyout_open';
import {
ADD_NOTE_BUTTON_TEST_ID,
ADD_NOTE_MARKDOWN_TEST_ID,
Expand Down Expand Up @@ -92,7 +93,7 @@ export const AddNote = memo(({ eventId }: AddNewNoteProps) => {
);

// if the flyout is open from a timeline and that timeline is saved, we automatically check the checkbox to associate the note to it
const isTimelineFlyout = useIsTimelineFlyoutOpen();
const isTimelineFlyout = useWhichFlyout() === Flyouts.timeline;

const [checked, setChecked] = useState<boolean>(true);
const onCheckboxChange = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ import {
import { useKibana as mockUseKibana } from '../../../../common/lib/kibana/__mocks__';
import { useKibana } from '../../../../common/lib/kibana';
import { FLYOUT_TOUR_CONFIG_ANCHORS } from '../../shared/utils/tour_step_config';
import { useIsTimelineFlyoutOpen } from '../../shared/hooks/use_is_timeline_flyout_open';
import { FLYOUT_TOUR_TEST_ID } from '../../shared/components/test_ids';
import { useWhichFlyout } from '../../shared/hooks/use_which_flyout';
import { Flyouts } from '../../shared/constants/flyouts';

jest.mock('../../../../common/lib/kibana');
jest.mock('../../shared/hooks/use_is_timeline_flyout_open');
jest.mock('../../shared/hooks/use_which_flyout');

const mockedUseKibana = mockUseKibana();

Expand Down Expand Up @@ -52,7 +53,7 @@ describe('<LeftPanelTour />', () => {
storage: storageMock,
},
});
(useIsTimelineFlyoutOpen as jest.Mock).mockReturnValue(false);
(useWhichFlyout as jest.Mock).mockReturnValue(Flyouts.securitySolution);

storageMock.clear();
});
Expand Down Expand Up @@ -105,7 +106,7 @@ describe('<LeftPanelTour />', () => {
});

it('should not render left panel tour for flyout in timeline', () => {
(useIsTimelineFlyoutOpen as jest.Mock).mockReturnValue(true);
(useWhichFlyout as jest.Mock).mockReturnValue(Flyouts.timeline);
storageMock.set('securitySolution.documentDetails.newFeaturesTour.v8.14', {
currentTourStep: 3,
isTourActive: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
*/

import React, { memo, useMemo } from 'react';
import { useWhichFlyout } from '../../shared/hooks/use_which_flyout';
import { getField } from '../../shared/utils';
import { EventKind } from '../../shared/constants/event_kinds';
import { useDocumentDetailsContext } from '../../shared/context';
import { FlyoutTour } from '../../shared/components/flyout_tour';
import { getLeftSectionTourSteps } from '../../shared/utils/tour_step_config';
import { useIsTimelineFlyoutOpen } from '../../shared/hooks/use_is_timeline_flyout_open';
import { Flyouts } from '../../shared/constants/flyouts';

/**
* Guided tour for the left panel in details flyout
Expand All @@ -20,7 +21,7 @@ export const LeftPanelTour = memo(() => {
const { getFieldsData, isPreview } = useDocumentDetailsContext();
const eventKind = getField(getFieldsData('event.kind'));
const isAlert = eventKind === EventKind.signal;
const isTimelineFlyoutOpen = useIsTimelineFlyoutOpen();
const isTimelineFlyoutOpen = useWhichFlyout() === Flyouts.timeline;
const showTour = isAlert && !isPreview && !isTimelineFlyoutOpen;

const tourStepContent = useMemo(() => getLeftSectionTourSteps(), []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ import {
import { useKibana as mockUseKibana } from '../../../../common/lib/kibana/__mocks__';
import { useKibana } from '../../../../common/lib/kibana';
import { FLYOUT_TOUR_CONFIG_ANCHORS } from '../../shared/utils/tour_step_config';
import { useIsTimelineFlyoutOpen } from '../../shared/hooks/use_is_timeline_flyout_open';
import { FLYOUT_TOUR_TEST_ID } from '../../shared/components/test_ids';
import { useTourContext } from '../../../../common/components/guided_onboarding_tour/tour';
import { casesPluginMock } from '@kbn/cases-plugin/public/mocks';
import { useWhichFlyout } from '../../shared/hooks/use_which_flyout';
import { Flyouts } from '../../shared/constants/flyouts';

jest.mock('../../../../common/lib/kibana');
jest.mock('../../shared/hooks/use_is_timeline_flyout_open');
jest.mock('../../shared/hooks/use_which_flyout');
jest.mock('../../../../common/components/guided_onboarding_tour/tour');

const mockedUseKibana = mockUseKibana();
Expand Down Expand Up @@ -59,7 +60,7 @@ describe('<RightPanelTour />', () => {
cases: mockCasesContract,
},
});
(useIsTimelineFlyoutOpen as jest.Mock).mockReturnValue(false);
(useWhichFlyout as jest.Mock).mockReturnValue(Flyouts.securitySolution);
(useTourContext as jest.Mock).mockReturnValue({ isTourShown: jest.fn(() => false) });
storageMock.clear();
});
Expand Down Expand Up @@ -112,7 +113,7 @@ describe('<RightPanelTour />', () => {
});

it('should not render tour for flyout in timeline', () => {
(useIsTimelineFlyoutOpen as jest.Mock).mockReturnValue(true);
(useWhichFlyout as jest.Mock).mockReturnValue(Flyouts.timeline);
const { queryByText, queryByTestId } = renderRightPanelTour({
...mockContextValue,
getFieldsData: () => '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import React, { memo, useMemo, useCallback } from 'react';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { useWhichFlyout } from '../../shared/hooks/use_which_flyout';
import { Flyouts } from '../../shared/constants/flyouts';
import { useDocumentDetailsContext } from '../../shared/context';
import { FlyoutTour } from '../../shared/components/flyout_tour';
import {
Expand All @@ -19,7 +21,6 @@ import {
DocumentDetailsRightPanelKey,
} from '../../shared/constants/panel_keys';
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';
import { useKibana } from '../../../../common/lib/kibana';
Expand All @@ -39,7 +40,7 @@ export const RightPanelTour = memo(() => {

const eventKind = getField(getFieldsData('event.kind'));
const isAlert = eventKind === EventKind.signal;
const isTimelineFlyoutOpen = useIsTimelineFlyoutOpen();
const isTimelineFlyoutOpen = useWhichFlyout() === Flyouts.timeline;
const showTour =
isAlert &&
!isPreview &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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.
*/

export enum Flyouts {
securitySolution = 'SecuritySolution',
timeline = 'Timeline',
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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 type { RenderHookResult } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react-hooks';
import { useWhichFlyout } from './use_which_flyout';
import { Flyouts } from '../constants/flyouts';

describe('useWhichFlyout', () => {
let hookResult: RenderHookResult<{}, string | null>;

beforeEach(() => {
jest.clearAllMocks();
window.location.search = '?';
});

describe('no flyout open', () => {
it('should return null if only none are the url', () => {
Object.defineProperty(window, 'location', {
value: {
search: '',
},
});

hookResult = renderHook(() => useWhichFlyout());

expect(hookResult.result.current).toEqual(null);
});

it('should return null if only they are the url but empty', () => {
Object.defineProperty(window, 'location', {
value: {
search: '?flyout=()&timelineFlyout=()',
},
});

hookResult = renderHook(() => useWhichFlyout());

expect(hookResult.result.current).toEqual(null);
});

it('should return null if only they are the url but params are empty preview', () => {
Object.defineProperty(window, 'location', {
value: {
search: '?flyout=(preview:!())&timelineFlyout=(preview:!())',
},
});

hookResult = renderHook(() => useWhichFlyout());

expect(hookResult.result.current).toEqual(null);
});
});

describe('SecuritySolution flyout open', () => {
it('should return SecuritySolution flyout if timelineFlyout is not in the url', () => {
Object.defineProperty(window, 'location', {
value: {
search:
'?flyout=(right:(id:document-details-right,params:(id:id,indexName:indexName,scopeId:scopeId)))',
},
});

hookResult = renderHook(() => useWhichFlyout());

expect(hookResult.result.current).toEqual(Flyouts.securitySolution);
});

it('should return SecuritySolution flyout if timelineFlyout is in the url but empty', () => {
Object.defineProperty(window, 'location', {
value: {
search:
'?flyout=(right:(id:document-details-right,params:(id:id,indexName:indexName,scopeId:scopeId)))&timelineFlyout=()',
},
});

hookResult = renderHook(() => useWhichFlyout());

expect(hookResult.result.current).toEqual(Flyouts.securitySolution);
});

it('should return SecuritySolution flyout if timelineFlyout is in the url but params are empty preview', () => {
window.location.search =
'http://app/security/alerts&flyout=(right:(id:document-details-right))&timelineFlyout=(preview:!())';

hookResult = renderHook(() => useWhichFlyout());

expect(hookResult.result.current).toEqual(Flyouts.securitySolution);
});
});

describe('Timeline flyout open', () => {
it('should return Timeline flyout if flyout and timelineFlyout are in the url', () => {
Object.defineProperty(window, 'location', {
value: {
search:
'?flyout=(right:(id:document-details-right,params:(id:id,indexName:indexName,scopeId:scopeId)))&timelineFlyout=(right:(id:document-details-right,params:(id:id,indexName:indexName,scopeId:scopeId)))',
},
});

hookResult = renderHook(() => useWhichFlyout());

expect(hookResult.result.current).toEqual(Flyouts.timeline);
});

it('should return Timeline flyout if only timelineFlyout is in the url', () => {
Object.defineProperty(window, 'location', {
value: {
search:
'?timelineFlyout=(right:(id:document-details-right,params:(id:id,indexName:indexName,scopeId:scopeId)))',
},
});

hookResult = renderHook(() => useWhichFlyout());

expect(hookResult.result.current).toEqual(Flyouts.timeline);
});
});
});
Loading

0 comments on commit 09d7cfd

Please sign in to comment.