From f4ddc9c6b1b6688bec728318c40dcd5c19af35e3 Mon Sep 17 00:00:00 2001 From: Luke G <11671118+lgestc@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:58:11 +0100 Subject: [PATCH] [Security Solution][Flyout] Don't share state using react context (#174666) ## Summary This is a first PR in series of improving the performance / dx of how we are doing state management in the flyout component. We used to share both state and api for the flyout using context api; now the url state / callbacks are just grouped under a single hook, and for the in-memory we are doing isolated redux store. Further PRs will involve splitting the api hook into separate portions for the state and callbacks (so that we dont re-render the components that just need the callback access when state changes), and some typing / code cleanups. --- packages/kbn-expandable-flyout/index.ts | 2 +- packages/kbn-expandable-flyout/src/actions.ts | 53 ++---- .../src/components/preview_section.test.tsx | 15 +- .../kbn-expandable-flyout/src/context.tsx | 15 +- .../src/context/memory_state_provider.tsx | 93 +++++++--- .../src/context/url_state_provider.tsx | 20 +-- .../src/index.stories.tsx | 26 ++- .../kbn-expandable-flyout/src/index.test.tsx | 91 +++++----- .../kbn-expandable-flyout/src/provider.tsx | 14 +- .../kbn-expandable-flyout/src/reducer.test.ts | 137 ++++++--------- packages/kbn-expandable-flyout/src/reducer.ts | 159 ++++++------------ packages/kbn-expandable-flyout/src/state.ts | 30 ++++ .../src/test/provider.tsx | 39 +++++ packages/kbn-expandable-flyout/src/types.ts | 4 +- packages/kbn-url-state/index.ts | 79 +++------ .../public/common/mock/test_providers.tsx | 6 +- .../risk_summary.stories.tsx | 12 +- .../preview/components/rule_preview.test.tsx | 7 +- .../components/rule_preview_title.test.tsx | 7 +- .../components/correlations_overview.test.tsx | 29 ++-- .../right/components/description.test.tsx | 20 ++- .../right/components/header_actions.test.tsx | 8 +- .../right/components/header_title.test.tsx | 11 +- .../highlighted_fields_cell.test.tsx | 23 ++- .../components/host_entity_overview.test.tsx | 22 ++- .../components/investigation_guide.test.tsx | 26 +-- .../components/investigation_section.test.tsx | 8 +- .../components/prevalence_overview.test.tsx | 22 ++- .../right/components/reason.test.tsx | 23 ++- .../right/components/response_button.test.tsx | 7 +- .../components/response_section.test.tsx | 12 +- .../right/components/session_preview.test.tsx | 11 +- .../right/components/status.test.tsx | 11 +- .../threat_intelligence_overview.test.tsx | 28 +-- .../components/user_entity_overview.test.tsx | 22 ++- .../visualizations_section.test.tsx | 11 +- .../host_right/content.stories.tsx | 12 +- .../user_right/content.stories.tsx | 12 +- .../components/flyout_navigation.stories.tsx | 35 ++-- .../components/flyout_navigation.test.tsx | 93 +++++----- 40 files changed, 610 insertions(+), 645 deletions(-) create mode 100644 packages/kbn-expandable-flyout/src/state.ts create mode 100644 packages/kbn-expandable-flyout/src/test/provider.tsx diff --git a/packages/kbn-expandable-flyout/index.ts b/packages/kbn-expandable-flyout/index.ts index 6de82033af04c..2134c34ac2670 100644 --- a/packages/kbn-expandable-flyout/index.ts +++ b/packages/kbn-expandable-flyout/index.ts @@ -13,6 +13,6 @@ export { useExpandableFlyoutContext, type ExpandableFlyoutContext } from './src/ export { ExpandableFlyoutProvider } from './src/provider'; export type { ExpandableFlyoutProps } from './src'; -export type { FlyoutPanelProps, PanelPath } from './src/types'; +export type { FlyoutPanelProps, PanelPath, ExpandableFlyoutApi } from './src/types'; export { EXPANDABLE_FLYOUT_URL_KEY } from './src/constants'; diff --git a/packages/kbn-expandable-flyout/src/actions.ts b/packages/kbn-expandable-flyout/src/actions.ts index aa8e813f8a845..ce3dd7e208b4c 100644 --- a/packages/kbn-expandable-flyout/src/actions.ts +++ b/packages/kbn-expandable-flyout/src/actions.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { createAction } from '@reduxjs/toolkit'; import { FlyoutPanelProps } from './types'; export enum ActionType { @@ -20,39 +21,19 @@ export enum ActionType { closeFlyout = 'close_flyout', } -export type Action = - | { - type: ActionType.openFlyout; - payload: { - right?: FlyoutPanelProps; - left?: FlyoutPanelProps; - preview?: FlyoutPanelProps; - }; - } - | { - type: ActionType.openRightPanel; - payload: FlyoutPanelProps; - } - | { - type: ActionType.openLeftPanel; - payload: FlyoutPanelProps; - } - | { - type: ActionType.openPreviewPanel; - payload: FlyoutPanelProps; - } - | { - type: ActionType.closeRightPanel; - } - | { - type: ActionType.closeLeftPanel; - } - | { - type: ActionType.closePreviewPanel; - } - | { - type: ActionType.previousPreviewPanel; - } - | { - type: ActionType.closeFlyout; - }; +export const openPanelsAction = createAction<{ + right?: FlyoutPanelProps; + left?: FlyoutPanelProps; + preview?: FlyoutPanelProps; +}>(ActionType.openFlyout); + +export const openRightPanelAction = createAction(ActionType.openRightPanel); +export const openLeftPanelAction = createAction(ActionType.openLeftPanel); +export const openPreviewPanelAction = createAction(ActionType.openPreviewPanel); + +export const closePanelsAction = createAction(ActionType.closeFlyout); +export const closeRightPanelAction = createAction(ActionType.closeRightPanel); +export const closeLeftPanelAction = createAction(ActionType.closeLeftPanel); +export const closePreviewPanelAction = createAction(ActionType.closePreviewPanel); + +export const previousPreviewPanelAction = createAction(ActionType.previousPreviewPanel); diff --git a/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx b/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx index 69bdd7050e64a..d3bd510266084 100644 --- a/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx +++ b/packages/kbn-expandable-flyout/src/components/preview_section.test.tsx @@ -14,7 +14,8 @@ import { PREVIEW_SECTION_CLOSE_BUTTON_TEST_ID, PREVIEW_SECTION_TEST_ID, } from './test_ids'; -import { ExpandableFlyoutContext, ExpandableFlyoutContextValue } from '../context'; +import { ExpandableFlyoutContextValue } from '../context'; +import { TestProvider } from '../test/provider'; describe('PreviewSection', () => { const context = { @@ -36,9 +37,9 @@ describe('PreviewSection', () => { const showBackButton = false; const { getByTestId } = render( - + - + ); expect(getByTestId(PREVIEW_SECTION_CLOSE_BUTTON_TEST_ID)).toBeInTheDocument(); @@ -48,9 +49,9 @@ describe('PreviewSection', () => { const showBackButton = true; const { getByTestId } = render( - + - + ); expect(getByTestId(PREVIEW_SECTION_BACK_BUTTON_TEST_ID)).toBeInTheDocument(); @@ -66,14 +67,14 @@ describe('PreviewSection', () => { }; const { getByTestId, getByText } = render( - + - + ); expect(getByTestId(`${PREVIEW_SECTION_TEST_ID}BannerPanel`)).toHaveClass( diff --git a/packages/kbn-expandable-flyout/src/context.tsx b/packages/kbn-expandable-flyout/src/context.tsx index 6deb5d2bede30..c0db2fbd42e17 100644 --- a/packages/kbn-expandable-flyout/src/context.tsx +++ b/packages/kbn-expandable-flyout/src/context.tsx @@ -7,9 +7,13 @@ */ import { createContext, useContext } from 'react'; -import { type ExpandableFlyoutContextValue } from './types'; +import { useFlyoutMemoryState } from './context/memory_state_provider'; +import { useFlyoutUrlState } from './context/url_state_provider'; +import { type ExpandableFlyoutApi } from './types'; -export type { ExpandableFlyoutContextValue }; +export type { ExpandableFlyoutApi as ExpandableFlyoutContextValue }; + +type ExpandableFlyoutContextValue = 'memory' | 'url'; export const ExpandableFlyoutContext = createContext( undefined @@ -18,7 +22,7 @@ export const ExpandableFlyoutContext = createContext { +export const useExpandableFlyoutContext = (): ExpandableFlyoutApi => { const contextValue = useContext(ExpandableFlyoutContext); if (!contextValue) { @@ -27,5 +31,8 @@ export const useExpandableFlyoutContext = (): ExpandableFlyoutContextValue => { ); } - return contextValue; + const memoryState = useFlyoutMemoryState(); + const urlState = useFlyoutUrlState(); + + return contextValue === 'memory' ? memoryState : urlState; }; diff --git a/packages/kbn-expandable-flyout/src/context/memory_state_provider.tsx b/packages/kbn-expandable-flyout/src/context/memory_state_provider.tsx index f70ae9dbbe8de..a4981c5a2122b 100644 --- a/packages/kbn-expandable-flyout/src/context/memory_state_provider.tsx +++ b/packages/kbn-expandable-flyout/src/context/memory_state_provider.tsx @@ -6,19 +6,48 @@ * Side Public License, v 1. */ -import React, { FC, PropsWithChildren, useCallback, useMemo, useReducer } from 'react'; -import { ActionType } from '../actions'; +import React, { createContext, FC, useCallback, useMemo } from 'react'; +import { + createDispatchHook, + createSelectorHook, + Provider as ReduxProvider, + ReactReduxContextValue, +} from 'react-redux'; +import { configureStore } from '@reduxjs/toolkit'; + import { reducer } from '../reducer'; -import type { ExpandableFlyoutContextValue, FlyoutPanelProps } from '../types'; -import { initialState } from '../reducer'; -import { ExpandableFlyoutContext } from '../context'; +import { initialState, State } from '../state'; +import type { ExpandableFlyoutApi, FlyoutPanelProps } from '../types'; +import { + closeLeftPanelAction, + closePanelsAction, + closePreviewPanelAction, + closeRightPanelAction, + openLeftPanelAction, + openPanelsAction, + openPreviewPanelAction, + openRightPanelAction, + previousPreviewPanelAction, +} from '../actions'; -/** - * In-memory state provider for the expandable flyout, for cases when we don't want changes to be persisted - * in the url. - */ -export const MemoryStateProvider: FC> = ({ children }) => { - const [state, dispatch] = useReducer(reducer, initialState); +export const store = configureStore({ + reducer, + devTools: process.env.NODE_ENV !== 'production', + preloadedState: {}, + enhancers: [], +}); + +export const Context = createContext>({ + store, + storeState: initialState, +}); + +const useDispatch = createDispatchHook(Context); +const useSelector = createSelectorHook(Context); + +export const useFlyoutMemoryState = (): ExpandableFlyoutApi => { + const state = useSelector((s) => s); + const dispatch = useDispatch(); const openPanels = useCallback( ({ @@ -29,41 +58,41 @@ export const MemoryStateProvider: FC> = ({ children }) => right?: FlyoutPanelProps; left?: FlyoutPanelProps; preview?: FlyoutPanelProps; - }) => dispatch({ type: ActionType.openFlyout, payload: { left, right, preview } }), + }) => dispatch(openPanelsAction({ right, left, preview })), [dispatch] ); const openRightPanel = useCallback( - (panel: FlyoutPanelProps) => dispatch({ type: ActionType.openRightPanel, payload: panel }), - [] + (panel: FlyoutPanelProps) => dispatch(openRightPanelAction(panel)), + [dispatch] ); const openLeftPanel = useCallback( - (panel: FlyoutPanelProps) => dispatch({ type: ActionType.openLeftPanel, payload: panel }), - [] + (panel: FlyoutPanelProps) => dispatch(openLeftPanelAction(panel)), + [dispatch] ); const openPreviewPanel = useCallback( - (panel: FlyoutPanelProps) => dispatch({ type: ActionType.openPreviewPanel, payload: panel }), - [] + (panel: FlyoutPanelProps) => dispatch(openPreviewPanelAction(panel)), + [dispatch] ); - const closeRightPanel = useCallback(() => dispatch({ type: ActionType.closeRightPanel }), []); + const closeRightPanel = useCallback(() => dispatch(closeRightPanelAction()), [dispatch]); - const closeLeftPanel = useCallback(() => dispatch({ type: ActionType.closeLeftPanel }), []); + const closeLeftPanel = useCallback(() => dispatch(closeLeftPanelAction()), [dispatch]); - const closePreviewPanel = useCallback(() => dispatch({ type: ActionType.closePreviewPanel }), []); + const closePreviewPanel = useCallback(() => dispatch(closePreviewPanelAction()), [dispatch]); const previousPreviewPanel = useCallback( - () => dispatch({ type: ActionType.previousPreviewPanel }), - [] + () => dispatch(previousPreviewPanelAction()), + [dispatch] ); const closePanels = useCallback(() => { - dispatch({ type: ActionType.closeFlyout }); - }, []); + dispatch(closePanelsAction()); + }, [dispatch]); - const contextValue: ExpandableFlyoutContextValue = useMemo( + const api: ExpandableFlyoutApi = useMemo( () => ({ panels: state, openFlyout: openPanels, @@ -90,9 +119,17 @@ export const MemoryStateProvider: FC> = ({ children }) => ] ); + return api; +}; + +/** + * In-memory state provider for the expandable flyout, for cases when we don't want changes to be persisted + * in the url. + */ +export const MemoryStateProvider: FC = ({ children }) => { return ( - + {children} - + ); }; diff --git a/packages/kbn-expandable-flyout/src/context/url_state_provider.tsx b/packages/kbn-expandable-flyout/src/context/url_state_provider.tsx index e6f81e23d2fb1..c26525cf0e594 100644 --- a/packages/kbn-expandable-flyout/src/context/url_state_provider.tsx +++ b/packages/kbn-expandable-flyout/src/context/url_state_provider.tsx @@ -6,18 +6,14 @@ * Side Public License, v 1. */ -import React, { FC, PropsWithChildren, useCallback, useMemo } from 'react'; -import { FlyoutPanelProps, ExpandableFlyoutContextValue } from '../types'; +import { useCallback, useMemo } from 'react'; +import { FlyoutPanelProps, ExpandableFlyoutApi } from '../types'; import { useRightPanel } from '../hooks/use_right_panel'; import { useLeftPanel } from '../hooks/use_left_panel'; import { usePreviewPanel } from '../hooks/use_preview_panel'; -import { ExpandableFlyoutContext } from '../context'; -import { State } from '../reducer'; +import { State } from '../state'; -/** - * Private component that manages flyout state with url query params - */ -export const UrlStateProvider: FC> = ({ children }) => { +export const useFlyoutUrlState = (): ExpandableFlyoutApi => { const { setRightPanelState, rightPanelState } = useRightPanel(); const { setLeftPanelState, leftPanelState } = useLeftPanel(); const { previewState, setPreviewState } = usePreviewPanel(); @@ -82,7 +78,7 @@ export const UrlStateProvider: FC> = ({ children }) => { setPreviewState([]); }, [setRightPanelState, setLeftPanelState, setPreviewState]); - const contextValue: ExpandableFlyoutContextValue = useMemo( + const contextValue: ExpandableFlyoutApi = useMemo( () => ({ panels, openFlyout: openPanels, @@ -109,9 +105,5 @@ export const UrlStateProvider: FC> = ({ children }) => { ] ); - return ( - - {children} - - ); + return contextValue; }; diff --git a/packages/kbn-expandable-flyout/src/index.stories.tsx b/packages/kbn-expandable-flyout/src/index.stories.tsx index 615621bf9a571..8c6bacb20adf6 100644 --- a/packages/kbn-expandable-flyout/src/index.stories.tsx +++ b/packages/kbn-expandable-flyout/src/index.stories.tsx @@ -19,7 +19,8 @@ import { EuiTitle, } from '@elastic/eui'; import { ExpandableFlyout } from '.'; -import { ExpandableFlyoutContext, ExpandableFlyoutContextValue } from './context'; +import { ExpandableFlyoutContextValue } from './context'; +import { TestProvider } from './test/provider'; export default { component: ExpandableFlyout, @@ -108,13 +109,12 @@ export const Right: Story = () => { left: {}, preview: [], }, - closeFlyout: () => window.alert('closeFlyout api'), } as unknown as ExpandableFlyoutContextValue; return ( - + - + ); }; @@ -129,13 +129,12 @@ export const Left: Story = () => { }, preview: [], }, - closeFlyout: () => window.alert('closeFlyout api'), } as unknown as ExpandableFlyoutContextValue; return ( - + - + ); }; @@ -154,14 +153,12 @@ export const Preview: Story = () => { }, ], }, - closePreviewPanel: () => window.alert('closePreviewPanel api'), - closeFlyout: () => window.alert('closeFlyout api'), } as unknown as ExpandableFlyoutContextValue; return ( - + - + ); }; @@ -183,14 +180,11 @@ export const MultiplePreviews: Story = () => { }, ], }, - closePreviewPanel: () => window.alert('closePreviewPanel api'), - previousPreviewPanel: () => window.alert('previousPreviewPanel api'), - closeFlyout: () => window.alert('closeFlyout api'), } as unknown as ExpandableFlyoutContextValue; return ( - + - + ); }; diff --git a/packages/kbn-expandable-flyout/src/index.test.tsx b/packages/kbn-expandable-flyout/src/index.test.tsx index 46ae2dce508f2..a2265adfa5eea 100644 --- a/packages/kbn-expandable-flyout/src/index.test.tsx +++ b/packages/kbn-expandable-flyout/src/index.test.tsx @@ -8,36 +8,37 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { ExpandableFlyoutContextValue, Panel } from './types'; + +import { Panel } from './types'; import { ExpandableFlyout } from '.'; import { LEFT_SECTION_TEST_ID, PREVIEW_SECTION_TEST_ID, RIGHT_SECTION_TEST_ID, } from './components/test_ids'; -import { ExpandableFlyoutContext } from './context'; +import { type State } from './state'; +import { TestProvider } from './test/provider'; +jest.mock('./context/url_state_provider'); -describe('ExpandableFlyout', () => { - const registeredPanels: Panel[] = [ - { - key: 'key', - component: () =>
{'component'}
, - }, - ]; +const registeredPanels: Panel[] = [ + { + key: 'key', + component: () =>
{'component'}
, + }, +]; +describe('ExpandableFlyout', () => { it(`shouldn't render flyout if no panels`, () => { const context = { - panels: { - right: undefined, - left: undefined, - preview: [], - }, - } as unknown as ExpandableFlyoutContextValue; + right: undefined, + left: undefined, + preview: [], + } as unknown as State; const result = render( - + - + ); expect(result.asFragment()).toMatchInlineSnapshot(``); @@ -45,19 +46,17 @@ describe('ExpandableFlyout', () => { it('should render right section', () => { const context = { - panels: { - right: { - id: 'key', - }, - left: {}, - preview: [], + right: { + id: 'key', }, - } as unknown as ExpandableFlyoutContextValue; + left: {}, + preview: [], + } as unknown as State; const { getByTestId } = render( - + - + ); expect(getByTestId(RIGHT_SECTION_TEST_ID)).toBeInTheDocument(); @@ -65,41 +64,37 @@ describe('ExpandableFlyout', () => { it('should render left section', () => { const context = { - panels: { - right: {}, - left: { - id: 'key', - }, - preview: [], + right: {}, + left: { + id: 'key', }, - } as unknown as ExpandableFlyoutContextValue; + preview: [], + } as unknown as State; const { getByTestId } = render( - + - + ); expect(getByTestId(LEFT_SECTION_TEST_ID)).toBeInTheDocument(); }); it('should render preview section', () => { - const context: ExpandableFlyoutContextValue = { - panels: { - right: {}, - left: {}, - preview: [ - { - id: 'key', - }, - ], - }, - } as unknown as ExpandableFlyoutContextValue; + const context = { + right: {}, + left: {}, + preview: [ + { + id: 'key', + }, + ], + } as State; const { getByTestId } = render( - + - + ); expect(getByTestId(PREVIEW_SECTION_TEST_ID)).toBeInTheDocument(); diff --git a/packages/kbn-expandable-flyout/src/provider.tsx b/packages/kbn-expandable-flyout/src/provider.tsx index a5acc25c13b7d..63c8aec6b9bf4 100644 --- a/packages/kbn-expandable-flyout/src/provider.tsx +++ b/packages/kbn-expandable-flyout/src/provider.tsx @@ -7,7 +7,7 @@ */ import React, { FC, PropsWithChildren } from 'react'; -import { UrlStateProvider } from './context/url_state_provider'; +import { ExpandableFlyoutContext } from './context'; import { MemoryStateProvider } from './context/memory_state_provider'; interface ExpandableFlyoutProviderProps { @@ -22,16 +22,16 @@ interface ExpandableFlyoutProviderProps { * Wrap your plugin with this context for the ExpandableFlyout React component. * Storage property allows you to specify how the flyout state works internally. * With "url", it will be persisted into url and thus allow for deep linking & will survive webpage reloads. - * "memory" is based on useReducer hook. The state is saved internally to the package. which means it will not be + * "memory" is based on an isolated redux context. The state is saved internally to the package, which means it will not be * persisted when sharing url or reloading browser pages. */ export const ExpandableFlyoutProvider: FC> = ({ children, storage = 'url', }) => { - if (storage === 'memory') { - return {children}; - } - - return {children}; + return ( + + {children} + + ); }; diff --git a/packages/kbn-expandable-flyout/src/reducer.test.ts b/packages/kbn-expandable-flyout/src/reducer.test.ts index 475566a059a80..92a9d2d8309e5 100644 --- a/packages/kbn-expandable-flyout/src/reducer.test.ts +++ b/packages/kbn-expandable-flyout/src/reducer.test.ts @@ -7,8 +7,19 @@ */ import { FlyoutPanelProps } from './types'; -import { initialState, reducer, State } from './reducer'; -import { Action, ActionType } from './actions'; +import { reducer } from './reducer'; +import { initialState, State } from './state'; +import { + closeLeftPanelAction, + closePanelsAction, + closePreviewPanelAction, + closeRightPanelAction, + openLeftPanelAction, + openPanelsAction, + openPreviewPanelAction, + openRightPanelAction, + previousPreviewPanelAction, +} from './actions'; const rightPanel1: FlyoutPanelProps = { id: 'right1', @@ -39,14 +50,11 @@ describe('reducer', () => { describe('should handle openFlyout action', () => { it('should add panels to empty state', () => { const state: State = initialState; - const action: Action = { - type: ActionType.openFlyout, - payload: { - right: rightPanel1, - left: leftPanel1, - preview: previewPanel1, - }, - }; + const action = openPanelsAction({ + right: rightPanel1, + left: leftPanel1, + preview: previewPanel1, + }); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -62,14 +70,11 @@ describe('reducer', () => { right: rightPanel1, preview: [previewPanel1, { id: 'preview' }], }; - const action: Action = { - type: ActionType.openFlyout, - payload: { - right: rightPanel2, - left: leftPanel2, - preview: previewPanel2, - }, - }; + const action = openPanelsAction({ + right: rightPanel2, + left: leftPanel2, + preview: previewPanel2, + }); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -85,12 +90,9 @@ describe('reducer', () => { right: rightPanel1, preview: [previewPanel1], }; - const action: Action = { - type: ActionType.openFlyout, - payload: { - right: rightPanel2, - }, - }; + const action = openPanelsAction({ + right: rightPanel2, + }); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -104,10 +106,7 @@ describe('reducer', () => { describe('should handle openRightPanel action', () => { it('should add right panel to empty state', () => { const state: State = initialState; - const action: Action = { - type: ActionType.openRightPanel, - payload: rightPanel1, - }; + const action = openRightPanelAction(rightPanel1); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -123,10 +122,7 @@ describe('reducer', () => { right: rightPanel1, preview: [previewPanel1], }; - const action: Action = { - type: ActionType.openRightPanel, - payload: rightPanel2, - }; + const action = openRightPanelAction(rightPanel2); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -140,10 +136,7 @@ describe('reducer', () => { describe('should handle openLeftPanel action', () => { it('should add left panel to empty state', () => { const state: State = initialState; - const action: Action = { - type: ActionType.openLeftPanel, - payload: leftPanel1, - }; + const action = openLeftPanelAction(leftPanel1); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -159,10 +152,7 @@ describe('reducer', () => { right: rightPanel1, preview: [previewPanel1], }; - const action: Action = { - type: ActionType.openLeftPanel, - payload: leftPanel2, - }; + const action = openLeftPanelAction(leftPanel2); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -176,10 +166,7 @@ describe('reducer', () => { describe('should handle openPreviewPanel action', () => { it('should add preview panel to empty state', () => { const state: State = initialState; - const action: Action = { - type: ActionType.openPreviewPanel, - payload: previewPanel1, - }; + const action = openPreviewPanelAction(previewPanel1); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -195,10 +182,7 @@ describe('reducer', () => { right: rightPanel1, preview: [previewPanel1], }; - const action: Action = { - type: ActionType.openPreviewPanel, - payload: previewPanel2, - }; + const action = openPreviewPanelAction(previewPanel2); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -212,9 +196,7 @@ describe('reducer', () => { describe('should handle closeRightPanel action', () => { it('should return empty state when removing right panel from empty state', () => { const state: State = initialState; - const action: Action = { - type: ActionType.closeRightPanel, - }; + const action = closeRightPanelAction(); const newState: State = reducer(state, action); expect(newState).toEqual(state); @@ -226,9 +208,7 @@ describe('reducer', () => { right: undefined, preview: [previewPanel1], }; - const action: Action = { - type: ActionType.closeRightPanel, - }; + const action = closeRightPanelAction(); const newState: State = reducer(state, action); expect(newState).toEqual(state); @@ -240,9 +220,8 @@ describe('reducer', () => { right: rightPanel1, preview: [previewPanel1], }; - const action: Action = { - type: ActionType.closeRightPanel, - }; + const action = closeRightPanelAction(); + const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -256,9 +235,7 @@ describe('reducer', () => { describe('should handle closeLeftPanel action', () => { it('should return empty state when removing left panel on empty state', () => { const state: State = initialState; - const action: Action = { - type: ActionType.closeLeftPanel, - }; + const action = closeLeftPanelAction(); const newState: State = reducer(state, action); expect(newState).toEqual(state); @@ -270,9 +247,7 @@ describe('reducer', () => { right: rightPanel1, preview: [], }; - const action: Action = { - type: ActionType.closeLeftPanel, - }; + const action = closeLeftPanelAction(); const newState: State = reducer(state, action); expect(newState).toEqual(state); @@ -284,9 +259,7 @@ describe('reducer', () => { right: rightPanel1, preview: [previewPanel1], }; - const action: Action = { - type: ActionType.closeLeftPanel, - }; + const action = closeLeftPanelAction(); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -300,9 +273,7 @@ describe('reducer', () => { describe('should handle closePreviewPanel action', () => { it('should return empty state when removing preview panel on empty state', () => { const state: State = initialState; - const action: Action = { - type: ActionType.closePreviewPanel, - }; + const action = closePreviewPanelAction(); const newState: State = reducer(state, action); expect(newState).toEqual(state); @@ -314,9 +285,7 @@ describe('reducer', () => { right: rightPanel1, preview: [], }; - const action: Action = { - type: ActionType.closePreviewPanel, - }; + const action = closePreviewPanelAction(); const newState: State = reducer(state, action); expect(newState).toEqual(state); @@ -328,9 +297,7 @@ describe('reducer', () => { right: leftPanel1, preview: [previewPanel1, previewPanel2], }; - const action: Action = { - type: ActionType.closePreviewPanel, - }; + const action = closePreviewPanelAction(); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -344,9 +311,7 @@ describe('reducer', () => { describe('should handle previousPreviewPanel action', () => { it('should return empty state when previous preview panel on an empty state', () => { const state: State = initialState; - const action: Action = { - type: ActionType.previousPreviewPanel, - }; + const action = previousPreviewPanelAction(); const newState: State = reducer(state, action); expect(newState).toEqual(state); @@ -358,9 +323,7 @@ describe('reducer', () => { right: rightPanel1, preview: [], }; - const action: Action = { - type: ActionType.previousPreviewPanel, - }; + const action = previousPreviewPanelAction(); const newState: State = reducer(state, action); expect(newState).toEqual(state); @@ -372,9 +335,7 @@ describe('reducer', () => { right: rightPanel1, preview: [previewPanel1, previewPanel2], }; - const action: Action = { - type: ActionType.previousPreviewPanel, - }; + const action = previousPreviewPanelAction(); const newState: State = reducer(state, action); expect(newState).toEqual({ @@ -388,9 +349,7 @@ describe('reducer', () => { describe('should handle closeFlyout action', () => { it('should return empty state when closing flyout on an empty state', () => { const state: State = initialState; - const action: Action = { - type: ActionType.closeFlyout, - }; + const action = closePanelsAction(); const newState: State = reducer(state, action); expect(newState).toEqual(initialState); @@ -402,9 +361,7 @@ describe('reducer', () => { right: rightPanel1, preview: [previewPanel1], }; - const action: Action = { - type: ActionType.closeFlyout, - }; + const action = closePanelsAction(); const newState: State = reducer(state, action); expect(newState).toEqual({ diff --git a/packages/kbn-expandable-flyout/src/reducer.ts b/packages/kbn-expandable-flyout/src/reducer.ts index 5b4c0675548aa..fe3d40ce9a797 100644 --- a/packages/kbn-expandable-flyout/src/reducer.ts +++ b/packages/kbn-expandable-flyout/src/reducer.ts @@ -6,107 +6,58 @@ * Side Public License, v 1. */ -import { FlyoutPanelProps } from './types'; -import { Action, ActionType } from './actions'; - -export interface State { - /** - * Panel to render in the left section - */ - left: FlyoutPanelProps | undefined; - /** - * Panel to render in the right section - */ - right: FlyoutPanelProps | undefined; - /** - * Panels to render in the preview section - */ - preview: FlyoutPanelProps[]; -} - -export const initialState: State = { - left: undefined, - right: undefined, - preview: [], -}; - -export function reducer(state: State, action: Action) { - switch (action.type) { - /** - * Open the flyout by replacing the entire state with new panels. - */ - case ActionType.openFlyout: { - const { left, right, preview } = action.payload; - return { - left, - right, - preview: preview ? [preview] : [], - }; - } - - /** - * Opens a right section by replacing the previous right panel with the new one. - */ - case ActionType.openRightPanel: { - return { ...state, right: action.payload }; - } - - /** - * Opens a left section by replacing the previous left panel with the new one. - */ - case ActionType.openLeftPanel: { - return { ...state, left: action.payload }; - } - - /** - * Opens a preview section by adding to the array of preview panels. - */ - case ActionType.openPreviewPanel: { - return { ...state, preview: [...state.preview, action.payload] }; - } - - /** - * Closes the right section by removing the right panel. - */ - case ActionType.closeRightPanel: { - return { ...state, right: undefined }; - } - - /** - * Close the left section by removing the left panel. - */ - case ActionType.closeLeftPanel: { - return { ...state, left: undefined }; - } - - /** - * Closes the preview section by removing all the preview panels. - */ - case ActionType.closePreviewPanel: { - return { ...state, preview: [] }; - } - - /** - * Navigates to the previous preview panel by removing the last entry in the array of preview panels. - */ - case ActionType.previousPreviewPanel: { - const p: FlyoutPanelProps[] = [...state.preview]; - p.pop(); - return { ...state, preview: p }; - } - - /** - * Close the flyout by removing all the panels. - */ - case ActionType.closeFlyout: { - return { - left: undefined, - right: undefined, - preview: [], - }; - } - - default: - return state; - } -} +import { createReducer } from '@reduxjs/toolkit'; +import { + openPanelsAction, + openLeftPanelAction, + openRightPanelAction, + closePanelsAction, + closeLeftPanelAction, + closePreviewPanelAction, + closeRightPanelAction, + previousPreviewPanelAction, + openPreviewPanelAction, +} from './actions'; +import { initialState } from './state'; + +export const reducer = createReducer(initialState, (builder) => { + builder.addCase(openPanelsAction, (state, { payload: { preview, left, right } }) => { + state.preview = preview ? [preview] : []; + state.right = right; + state.left = left; + }); + + builder.addCase(openLeftPanelAction, (state, { payload }) => { + state.left = payload; + }); + + builder.addCase(openRightPanelAction, (state, { payload }) => { + state.right = payload; + }); + + builder.addCase(openPreviewPanelAction, (state, { payload }) => { + state.preview.push(payload); + }); + + builder.addCase(previousPreviewPanelAction, (state) => { + state.preview.pop(); + }); + + builder.addCase(closePanelsAction, (state) => { + state.preview = []; + state.right = undefined; + state.left = undefined; + }); + + builder.addCase(closeLeftPanelAction, (state) => { + state.left = undefined; + }); + + builder.addCase(closeRightPanelAction, (state) => { + state.right = undefined; + }); + + builder.addCase(closePreviewPanelAction, (state) => { + state.preview = []; + }); +}); diff --git a/packages/kbn-expandable-flyout/src/state.ts b/packages/kbn-expandable-flyout/src/state.ts new file mode 100644 index 0000000000000..75f8269a82962 --- /dev/null +++ b/packages/kbn-expandable-flyout/src/state.ts @@ -0,0 +1,30 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FlyoutPanelProps } from './types'; + +export interface State { + /** + * Panel to render in the left section + */ + left: FlyoutPanelProps | undefined; + /** + * Panel to render in the right section + */ + right: FlyoutPanelProps | undefined; + /** + * Panels to render in the preview section + */ + preview: FlyoutPanelProps[]; +} + +export const initialState: State = { + left: undefined, + right: undefined, + preview: [], +}; diff --git a/packages/kbn-expandable-flyout/src/test/provider.tsx b/packages/kbn-expandable-flyout/src/test/provider.tsx new file mode 100644 index 0000000000000..eab1d94fc0bbd --- /dev/null +++ b/packages/kbn-expandable-flyout/src/test/provider.tsx @@ -0,0 +1,39 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Provider as ReduxProvider } from 'react-redux'; +import { configureStore } from '@reduxjs/toolkit'; +import React, { FC, PropsWithChildren } from 'react'; +import { reducer } from '../reducer'; +import { Context } from '../context/memory_state_provider'; +import { ExpandableFlyoutContext } from '../context'; +import { initialState, State } from '../state'; + +interface TestProviderProps { + state?: State; +} + +export const TestProvider: FC> = ({ + children, + state = initialState, +}) => { + const store = configureStore({ + reducer, + devTools: false, + preloadedState: state, + enhancers: [], + }); + + return ( + + + {children} + + + ); +}; diff --git a/packages/kbn-expandable-flyout/src/types.ts b/packages/kbn-expandable-flyout/src/types.ts index 92c4af79024b8..2d95426abddec 100644 --- a/packages/kbn-expandable-flyout/src/types.ts +++ b/packages/kbn-expandable-flyout/src/types.ts @@ -7,9 +7,9 @@ */ import React from 'react'; -import { State } from './reducer'; +import { State } from './state'; -export interface ExpandableFlyoutContextValue { +export interface ExpandableFlyoutApi { /** * Right, left and preview panels */ diff --git a/packages/kbn-url-state/index.ts b/packages/kbn-url-state/index.ts index 17c90fdc8d2aa..7bfb80938c659 100644 --- a/packages/kbn-url-state/index.ts +++ b/packages/kbn-url-state/index.ts @@ -9,20 +9,7 @@ import { useCallback, useEffect, useState } from 'react'; import { encode, decode, RisonValue } from '@kbn/rison'; import { stringify, parse } from 'query-string'; - -interface StateCache { - namespaces: Record>; - timeoutHandle: number; -} - -/** - * Temporary cache for state stored in the URL. This will be serialized to the URL - * in a single batched update to avoid excessive history entries. - */ -const cache: StateCache = { - namespaces: {}, - timeoutHandle: 0, -}; +import { merge } from 'lodash'; const CUSTOM_URL_EVENT = 'url:update' as const; @@ -45,10 +32,6 @@ const URL_CHANGE_EVENTS: string[] = ['popstate', CUSTOM_URL_EVENT]; * @param key sub key of the query param */ export const useUrlState = (urlNamespace: string, key: string) => { - if (!cache.namespaces[urlNamespace]) { - cache.namespaces[urlNamespace] = {}; - } - const [internalValue, setInternalValue] = useState(undefined); useEffect(() => { @@ -60,7 +43,6 @@ export const useUrlState = (urlNamespace: string, key: string) => { const decodedState = param ? decode(param) : ({} as Record); const decodedValue = (decodedState as Record | undefined)?.[key]; - cache.namespaces[urlNamespace][key] = decodedValue; setInternalValue(decodedValue as unknown as T); }; @@ -72,50 +54,45 @@ export const useUrlState = (urlNamespace: string, key: string) => { }, [key, urlNamespace]); const setValue = useCallback( - (updatedValue: T | undefined) => { - const currentValue = cache.namespaces[urlNamespace][key]; + (newValue: T | undefined) => { + const queryParams = parse(location.search) as any; + const currentNsValue = ( + queryParams?.[urlNamespace] ? decode(queryParams?.[urlNamespace]) : {} + ) as any; + + const currentValue = currentNsValue?.[key]; const canSpread = - typeof updatedValue === 'object' && + typeof newValue === 'object' && typeof currentValue === 'object' && - !Array.isArray(updatedValue) && + !Array.isArray(newValue) && !Array.isArray(currentValue); - cache.namespaces[urlNamespace][key] = canSpread - ? ({ ...currentValue, ...updatedValue } as unknown as T) - : (updatedValue as unknown as T); + const upatedValueToStoreAtKey = canSpread + ? (merge(currentValue, newValue) as unknown as T) + : (newValue as unknown as T); - // This batches updates to the URL state to avoid excessive history entries - if (cache.timeoutHandle) { - window.clearTimeout(cache.timeoutHandle); + if (upatedValueToStoreAtKey) { + currentNsValue[key] = upatedValueToStoreAtKey; + } else { + delete currentNsValue[key]; } - // The push state call is delayed to make sure that multiple calls to setValue - // within a short period of time are batched together. - cache.timeoutHandle = window.setTimeout(() => { - const searchParams = parse(location.search); + queryParams[urlNamespace] = encodeURIComponent(encode(currentNsValue)); - for (const ns in cache.namespaces) { - if (!Object.prototype.hasOwnProperty.call(cache.namespaces, ns)) { - continue; - } - searchParams[ns] = encodeURIComponent(encode(cache.namespaces[ns])); - } + // NOTE: don't re-encode the entire url params string + const newSearch = stringify(queryParams, { encode: false }); - // NOTE: don't re-encode the entire url params string - const newSearch = stringify(searchParams, { encode: false }); - - if (window.location.search === newSearch) { - return; - } + if (window.location.search === newSearch) { + return; + } - const newUrl = `${window.location.hash}?${newSearch}`; + const newUrl = `${window.location.hash}?${newSearch}`; - window.history.pushState({}, '', newUrl); - // This custom event is used to notify other instances - // of this hook that the URL has changed. - window.dispatchEvent(new Event(CUSTOM_URL_EVENT)); - }, 0); + window.history.pushState({}, '', newUrl); + // This custom event is used to notify other instances + // of this hook that the URL has changed. + window.dispatchEvent(new Event(CUSTOM_URL_EVENT)); }, [key, urlNamespace] ); diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx index 03dc789a41ad1..3dbb9636cb739 100644 --- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx @@ -20,7 +20,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { Action } from '@kbn/ui-actions-plugin/public'; import { CellActionsProvider } from '@kbn/cell-actions'; -import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout'; +import { TestProvider as ExpandableFlyoutTestProvider } from '@kbn/expandable-flyout/src/test/provider'; import { useKibana } from '../lib/kibana'; import { UpsellingProvider } from '../components/upselling_provider'; import { MockAssistantProvider } from './mock_assistant_provider'; @@ -81,7 +81,7 @@ export const TestProvidersComponent: React.FC = ({ - + Promise.resolve(cellActions)} @@ -89,7 +89,7 @@ export const TestProvidersComponent: React.FC = ({ {children} - + diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.stories.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.stories.tsx index 0f00959845ff3..cece761cd9d6f 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.stories.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.stories.tsx @@ -7,8 +7,7 @@ import React from 'react'; import type { Story } from '@storybook/react'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; import { StorybookProviders } from '../../../common/mock/storybook_providers'; import { mockRiskScoreState } from '../../../timelines/components/side_panel/new_user_detail/__mocks__'; import { RiskSummary } from './risk_summary'; @@ -18,15 +17,10 @@ export default { title: 'Components/RiskSummary', }; -const flyoutContextValue = { - openLeftPanel: () => window.alert('openLeftPanel called'), - panels: {}, -} as unknown as ExpandableFlyoutContextValue; - export const Default: Story = () => { return ( - +
{}} @@ -34,7 +28,7 @@ export const Default: Story = () => { queryId={'testQuery'} />
-
+
); }; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview.test.tsx index 78f8dff0e3211..f6cac8f48a65b 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview.test.tsx @@ -10,11 +10,10 @@ import { act, render } from '@testing-library/react'; import { RulePreview } from './rule_preview'; import { PreviewPanelContext } from '../context'; import { mockContextValue } from '../mocks/mock_context'; -import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { ThemeProvider } from 'styled-components'; import { getMockTheme } from '../../../../common/lib/kibana/kibana_react.mock'; import { TestProviders } from '../../../../common/mock'; +import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; import { useRuleWithFallback } from '../../../../detection_engine/rule_management/logic/use_rule_with_fallback'; import { getStepsData } from '../../../../detections/pages/detection_engine/rules/helpers'; import { @@ -58,11 +57,11 @@ const renderRulePreview = () => render( - + - + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview_title.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview_title.test.tsx index 439db1fa98cc2..f40dbb964268f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview_title.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/preview/components/rule_preview_title.test.tsx @@ -9,8 +9,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import type { RulePreviewTitleProps } from './rule_preview_title'; import { RulePreviewTitle } from './rule_preview_title'; -import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { TestProvider as ExpandableFlyoutTestProvider } from '@kbn/expandable-flyout/src/test/provider'; import { TestProviders } from '../../../../common/mock'; import type { Rule } from '../../../../detection_engine/rule_management/logic'; import { @@ -28,9 +27,9 @@ const defaultProps = { const renderRulePreviewTitle = (props: RulePreviewTitleProps) => render( - + - + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx index 90c14aa972afb..6f9f4dd9f7a9c 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/correlations_overview.test.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { render } from '@testing-library/react'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import type { ExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { RightPanelContext } from '../context'; import { TestProviders } from '../../../../common/mock'; import { CorrelationsOverview } from './correlations_overview'; @@ -84,7 +84,20 @@ const renderCorrelationsOverview = (contextValue: RightPanelContext) => ( const NO_DATA_MESSAGE = 'No correlations data available.'; +const flyoutContextValue = { + openLeftPanel: jest.fn(), +} as unknown as ExpandableFlyoutApi; + +jest.mock('@kbn/expandable-flyout', () => ({ + useExpandableFlyoutContext: jest.fn(), + ExpandableFlyoutProvider: ({ children }: React.PropsWithChildren<{}>) => <>{children}, +})); + describe('', () => { + beforeAll(() => { + jest.mocked(useExpandableFlyoutContext).mockReturnValue(flyoutContextValue); + }); + it('should render wrapper component', () => { jest.mocked(useShowRelatedAlertsByAncestry).mockReturnValue({ show: false }); jest.mocked(useShowRelatedAlertsBySameSourceEvent).mockReturnValue({ show: false }); @@ -181,17 +194,11 @@ describe('', () => { }); it('should navigate to the left section Insights tab when clicking on button', () => { - const flyoutContextValue = { - openLeftPanel: jest.fn(), - } as unknown as ExpandableFlyoutContextValue; - const { getByTestId } = render( - - - - - + + + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.test.tsx index 81d6993e805f5..110780664333a 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/description.test.tsx @@ -16,11 +16,13 @@ import { import { Description } from './description'; import { RightPanelContext } from '../context'; import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; import { DocumentDetailsPreviewPanelKey } from '../../preview'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/types'; import { i18n } from '@kbn/i18n'; +import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import type { ExpandableFlyoutApi } from '@kbn/expandable-flyout'; + +jest.mock('@kbn/expandable-flyout', () => ({ useExpandableFlyoutContext: jest.fn() })); const ruleUuid = { category: 'kibana', @@ -48,7 +50,7 @@ const ruleName = { const flyoutContextValue = { openPreviewPanel: jest.fn(), -} as unknown as ExpandableFlyoutContextValue; +} as unknown as ExpandableFlyoutApi; const panelContextValue = (dataFormattedForFieldBrowser: TimelineEventsDetailsItem[]) => ({ @@ -62,17 +64,19 @@ const panelContextValue = (dataFormattedForFieldBrowser: TimelineEventsDetailsIt const renderDescription = (panelContext: RightPanelContext) => render( - - - - - + + + ); const NO_DATA_MESSAGE = "There's no description for this rule."; describe('', () => { + beforeAll(() => { + jest.mocked(useExpandableFlyoutContext).mockReturnValue(flyoutContextValue); + }); + it('should render the component', () => { const { getByTestId } = renderDescription( panelContextValue([ruleUuid, ruleDescription, ruleName]) diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_actions.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_actions.test.tsx index 1e052aa170347..8f564b8266332 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_actions.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_actions.test.tsx @@ -7,8 +7,6 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { copyToClipboard } from '@elastic/eui'; import { RightPanelContext } from '../context'; import { SHARE_BUTTON_TEST_ID, CHAT_BUTTON_TEST_ID } from './test_ids'; @@ -19,6 +17,7 @@ import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_f import { TestProvidersComponent } from '../../../../common/mock'; import { useGetAlertDetailsFlyoutLink } from '../../../../timelines/components/side_panel/event_details/use_get_alert_details_flyout_link'; import { URL_PARAM_KEY } from '../../../../common/hooks/use_url_state'; +import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout'; jest.mock('../../../../common/lib/kibana'); jest.mock('../hooks/use_assistant'); @@ -33,7 +32,6 @@ jest.mock('@elastic/eui', () => ({ })); const alertUrl = 'https://example.com/alert'; -const flyoutContextValue = {} as unknown as ExpandableFlyoutContextValue; const mockContextValue = { dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, getFieldsData: jest.fn().mockImplementation(mockGetFieldsData), @@ -42,11 +40,11 @@ const mockContextValue = { const renderHeaderActions = (contextValue: RightPanelContext) => render( - + - + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.test.tsx index 9c3157f9d5d0a..75ddf6ed6dec4 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.test.tsx @@ -7,8 +7,6 @@ import React from 'react'; import { render } from '@testing-library/react'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; import { RISK_SCORE_VALUE_TEST_ID, @@ -29,7 +27,6 @@ moment.suppressDeprecationWarnings = true; moment.tz.setDefault('UTC'); const dateFormat = 'MMM D, YYYY @ HH:mm:ss.SSS'; -const flyoutContextValue = {} as unknown as ExpandableFlyoutContextValue; const mockContextValue = { dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, getFieldsData: jest.fn().mockImplementation(mockGetFieldsData), @@ -38,11 +35,9 @@ const mockContextValue = { const renderHeader = (contextValue: RightPanelContext) => render( - - - - - + + + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx index b5a1e0f364281..328eb3714f7bd 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.test.tsx @@ -13,21 +13,26 @@ import { HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID, } from './test_ids'; import { HighlightedFieldsCell } from './highlighted_fields_cell'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { TestProviders } from '../../../../common/mock'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; import { useGetEndpointDetails } from '../../../../management/hooks'; import { useSentinelOneAgentData } from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation'; +import { useExpandableFlyoutContext, type ExpandableFlyoutApi } from '@kbn/expandable-flyout'; jest.mock('../../../../management/hooks'); jest.mock('../../../../detections/components/host_isolation/use_sentinelone_host_isolation'); +jest.mock('@kbn/expandable-flyout', () => ({ + useExpandableFlyoutContext: jest.fn(), + ExpandableFlyoutProvider: ({ children }: React.PropsWithChildren<{}>) => <>{children}, +})); + const flyoutContextValue = { openLeftPanel: jest.fn(), -} as unknown as ExpandableFlyoutContextValue; +} as unknown as ExpandableFlyoutApi; + const panelContextValue = { eventId: 'event id', indexName: 'indexName', @@ -36,14 +41,16 @@ const panelContextValue = { const renderHighlightedFieldsCell = (values: string[], field: string) => render( - - - - - + + + ); describe('', () => { + beforeAll(() => { + jest.mocked(useExpandableFlyoutContext).mockReturnValue(flyoutContextValue); + }); + it('should render a basic cell', () => { const { getByTestId } = render(); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx index f7fc4c2a22d90..b1e1811e583c3 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx @@ -20,8 +20,7 @@ import { import { RightPanelContext } from '../context'; import { mockContextValue } from '../mocks/mock_context'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { useExpandableFlyoutContext, type ExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score'; @@ -41,9 +40,14 @@ const panelContextValue = { dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, }; +jest.mock('@kbn/expandable-flyout', () => ({ + useExpandableFlyoutContext: jest.fn(), + ExpandableFlyoutProvider: ({ children }: React.PropsWithChildren<{}>) => <>{children}, +})); + const flyoutContextValue = { openLeftPanel: jest.fn(), -} as unknown as ExpandableFlyoutContextValue; +} as unknown as ExpandableFlyoutApi; const mockUseGlobalTime = jest.fn().mockReturnValue({ from, to }); jest.mock('../../../../common/containers/use_global_time', () => { @@ -71,15 +75,17 @@ jest.mock('../../../../common/containers/use_first_last_seen'); const renderHostEntityContent = () => render( - - - - - + + + ); describe('', () => { + beforeAll(() => { + jest.mocked(useExpandableFlyoutContext).mockReturnValue(flyoutContextValue); + }); + describe('license is valid', () => { it('should render os family and host risk level', () => { mockUseHostDetails.mockReturnValue([false, { hostDetails: hostData }]); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx index d2444248d4202..eb30e2c3ad929 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_guide.test.tsx @@ -17,11 +17,13 @@ import { } from './test_ids'; import { mockContextValue } from '../mocks/mock_context'; import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import type { ExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { useInvestigationGuide } from '../../shared/hooks/use_investigation_guide'; import { LeftPanelInvestigationTab, DocumentDetailsLeftPanelKey } from '../../left'; jest.mock('../../shared/hooks/use_investigation_guide'); +jest.mock('@kbn/expandable-flyout', () => ({ useExpandableFlyoutContext: jest.fn() })); const NO_DATA_MESSAGE = 'Investigation guideThere’s no investigation guide for this rule.'; const PREVIEW_MESSAGE = 'Investigation guide is not available in alert preview.'; @@ -29,15 +31,19 @@ const PREVIEW_MESSAGE = 'Investigation guide is not available in alert preview.' const renderInvestigationGuide = () => render( - - - - - + + + ); describe('', () => { + beforeAll(() => { + jest.mocked(useExpandableFlyoutContext).mockReturnValue({ + openLeftPanel: mockFlyoutContextValue.openLeftPanel, + } as unknown as ExpandableFlyoutApi); + }); + it('should render investigation guide button correctly', () => { (useInvestigationGuide as jest.Mock).mockReturnValue({ loading: false, @@ -101,11 +107,9 @@ describe('', () => { it('should render preview message when flyout is in preview', () => { const { queryByTestId, getByTestId } = render( - - - - - + + + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_section.test.tsx index 6775ba2e00b6a..be223f1afd986 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/investigation_section.test.tsx @@ -14,14 +14,12 @@ import { } from './test_ids'; import { RightPanelContext } from '../context'; import { InvestigationSection } from './investigation_section'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { useRuleWithFallback } from '../../../../detection_engine/rule_management/logic/use_rule_with_fallback'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; +import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; jest.mock('../../../../detection_engine/rule_management/logic/use_rule_with_fallback'); -const flyoutContextValue = {} as unknown as ExpandableFlyoutContextValue; const panelContextValue = { dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser.filter( (d) => d.field !== 'kibana.alert.rule.type' @@ -31,11 +29,11 @@ const panelContextValue = { const renderInvestigationSection = (expanded: boolean = false) => render( - + - + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx index 5bdca0ec3dfa2..dcb73b5e59b3a 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/prevalence_overview.test.tsx @@ -5,8 +5,6 @@ * 2.0. */ -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { render } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; import { RightPanelContext } from '../context'; @@ -24,6 +22,7 @@ import { } from '../../../shared/components/test_ids'; import { usePrevalence } from '../../shared/hooks/use_prevalence'; import { mockContextValue } from '../mocks/mock_context'; +import { type ExpandableFlyoutApi, useExpandableFlyoutContext } from '@kbn/expandable-flyout'; jest.mock('../../shared/hooks/use_prevalence'); @@ -36,20 +35,27 @@ const NO_DATA_MESSAGE = 'No prevalence data available.'; const flyoutContextValue = { openLeftPanel: jest.fn(), -} as unknown as ExpandableFlyoutContextValue; +} as unknown as ExpandableFlyoutApi; + +jest.mock('@kbn/expandable-flyout', () => ({ + useExpandableFlyoutContext: jest.fn(), + ExpandableFlyoutProvider: ({ children }: React.PropsWithChildren<{}>) => <>{children}, +})); const renderPrevalenceOverview = (contextValue: RightPanelContext = mockContextValue) => render( - - - - - + + + ); describe('', () => { + beforeAll(() => { + jest.mocked(useExpandableFlyoutContext).mockReturnValue(flyoutContextValue); + }); + it('should render wrapper component', () => { (usePrevalence as jest.Mock).mockReturnValue({ loading: false, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.test.tsx index 9b459acbaf327..47c8fd87a538a 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/reason.test.tsx @@ -12,15 +12,20 @@ import { REASON_DETAILS_PREVIEW_BUTTON_TEST_ID, REASON_TITLE_TEST_ID } from './t import { Reason } from './reason'; import { RightPanelContext } from '../context'; import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { DocumentDetailsPreviewPanelKey } from '../../preview'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/types'; import { i18n } from '@kbn/i18n'; +import { type ExpandableFlyoutApi, useExpandableFlyoutContext } from '@kbn/expandable-flyout'; + const flyoutContextValue = { openPreviewPanel: jest.fn(), -} as unknown as ExpandableFlyoutContextValue; +} as unknown as ExpandableFlyoutApi; + +jest.mock('@kbn/expandable-flyout', () => ({ + useExpandableFlyoutContext: jest.fn(), + ExpandableFlyoutProvider: ({ children }: React.PropsWithChildren<{}>) => <>{children}, +})); const panelContextValue = { eventId: 'event id', @@ -33,17 +38,19 @@ const panelContextValue = { const renderReason = (panelContext: RightPanelContext = panelContextValue) => render( - - - - - + + + ); const NO_DATA_MESSAGE = "There's no source event information for this alert."; describe('', () => { + beforeAll(() => { + jest.mocked(useExpandableFlyoutContext).mockReturnValue(flyoutContextValue); + }); + it('should render the component for alert', () => { const { getByTestId } = renderReason(); expect(getByTestId(REASON_TITLE_TEST_ID)).toBeInTheDocument(); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_button.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_button.test.tsx index afab956e4d33c..2c0e5a5d5574a 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_button.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_button.test.tsx @@ -11,10 +11,9 @@ import { render } from '@testing-library/react'; import { RightPanelContext } from '../context'; import { RESPONSE_BUTTON_TEST_ID, RESPONSE_EMPTY_TEST_ID } from './test_ids'; import { mockContextValue } from '../mocks/mock_context'; -import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { ResponseButton } from './response_button'; import type { SearchHit } from '../../../../../common/search_strategy'; +import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; const mockValidSearchHit = { fields: { @@ -34,11 +33,11 @@ const mockValidSearchHit = { const renderResponseButton = (panelContextValue: RightPanelContext = mockContextValue) => render( - + - + ); describe('', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.test.tsx index d521155ea1631..9cd09e0edbda7 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/response_section.test.tsx @@ -10,23 +10,21 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; import { RESPONSE_SECTION_CONTENT_TEST_ID, RESPONSE_SECTION_HEADER_TEST_ID } from './test_ids'; import { RightPanelContext } from '../context'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { ResponseSection } from './response_section'; +import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; const PREVIEW_MESSAGE = 'Response is not available in alert preview.'; -const flyoutContextValue = {} as unknown as ExpandableFlyoutContextValue; const panelContextValue = {} as unknown as RightPanelContext; const renderResponseSection = () => render( - + - + ); @@ -54,11 +52,11 @@ describe('', () => { it('should render preview message if flyout is in preview', () => { const { getByTestId } = render( - + - + ); getByTestId(RESPONSE_SECTION_HEADER_TEST_ID).click(); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview.test.tsx index 525ca03dbc045..f7bc398671559 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview.test.tsx @@ -10,16 +10,11 @@ import { useProcessData } from '../hooks/use_process_data'; import { SessionPreview } from './session_preview'; import { TestProviders } from '../../../../common/mock'; import React from 'react'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; +import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; jest.mock('../hooks/use_process_data'); -const flyoutContextValue = { - openLeftPanel: jest.fn(), -} as unknown as ExpandableFlyoutContextValue; - const panelContextValue = { eventId: 'event id', indexName: 'indexName', @@ -30,11 +25,11 @@ const panelContextValue = { const renderSessionPreview = () => render( - + - + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.test.tsx index 1c9412deab05a..82ebab4f962de 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.test.tsx @@ -7,29 +7,24 @@ import React from 'react'; import { render } from '@testing-library/react'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; import { DocumentStatus } from './status'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { TestProviders } from '../../../../common/mock'; import { useAlertsActions } from '../../../../detections/components/alerts_table/timeline_actions/use_alerts_actions'; import { STATUS_BUTTON_TEST_ID } from './test_ids'; +import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; jest.mock('../../../../detections/components/alerts_table/timeline_actions/use_alerts_actions'); -const flyoutContextValue = { - closeFlyout: jest.fn(), -} as unknown as ExpandableFlyoutContextValue; - const renderStatus = (contextValue: RightPanelContext) => render( - + - + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx index d752df3d9350e..09ec09c8e476b 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/threat_intelligence_overview.test.tsx @@ -7,8 +7,7 @@ import React from 'react'; import { render } from '@testing-library/react'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { useExpandableFlyoutContext, type ExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { RightPanelContext } from '../context'; import { TestProviders } from '../../../../common/mock'; import { ThreatIntelligenceOverview } from './threat_intelligence_overview'; @@ -48,6 +47,11 @@ const panelContextValue = { dataFormattedForFieldBrowser: [], } as unknown as RightPanelContext; +jest.mock('@kbn/expandable-flyout', () => ({ + useExpandableFlyoutContext: jest.fn(), + ExpandableFlyoutProvider: ({ children }: React.PropsWithChildren<{}>) => <>{children}, +})); + const renderThreatIntelligenceOverview = (contextValue: RightPanelContext) => ( @@ -56,7 +60,15 @@ const renderThreatIntelligenceOverview = (contextValue: RightPanelContext) => ( ); +const flyoutContextValue = { + openLeftPanel: jest.fn(), +} as unknown as ExpandableFlyoutApi; + describe('', () => { + beforeAll(() => { + jest.mocked(useExpandableFlyoutContext).mockReturnValue(flyoutContextValue); + }); + it('should render wrapper component', () => { (useFetchThreatIntelligence as jest.Mock).mockReturnValue({ loading: false, @@ -146,17 +158,11 @@ describe('', () => { threatMatchesCount: 1, threatEnrichmentsCount: 1, }); - const flyoutContextValue = { - openLeftPanel: jest.fn(), - } as unknown as ExpandableFlyoutContextValue; - const { getByTestId } = render( - - - - - + + + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx index b36c21faf3f73..4102a1fa9b5ec 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx @@ -19,12 +19,11 @@ import { import { useObservedUserDetails } from '../../../../explore/users/containers/users/observed_details'; import { mockContextValue } from '../mocks/mock_context'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { RightPanelContext } from '../context'; import { LeftPanelInsightsTab, DocumentDetailsLeftPanelKey } from '../../left'; import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score'; +import { type ExpandableFlyoutApi, useExpandableFlyoutContext } from '@kbn/expandable-flyout'; const userName = 'user'; const domain = 'n54bg2lfc7'; @@ -41,9 +40,14 @@ const panelContextValue = { dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, }; +jest.mock('@kbn/expandable-flyout', () => ({ + useExpandableFlyoutContext: jest.fn(), + ExpandableFlyoutProvider: ({ children }: React.PropsWithChildren<{}>) => <>{children}, +})); + const flyoutContextValue = { openLeftPanel: jest.fn(), -} as unknown as ExpandableFlyoutContextValue; +} as unknown as ExpandableFlyoutApi; const mockUseGlobalTime = jest.fn().mockReturnValue({ from, to }); jest.mock('../../../../common/containers/use_global_time', () => { @@ -78,6 +82,10 @@ const renderUserEntityOverview = () => ); describe('', () => { + beforeAll(() => { + jest.mocked(useExpandableFlyoutContext).mockReturnValue(flyoutContextValue); + }); + describe('license is valid', () => { it('should render user domain and user risk level', () => { mockUseUserDetails.mockReturnValue([false, { userDetails: userData }]); @@ -160,11 +168,9 @@ describe('', () => { const { getByTestId } = render( - - - - - + + + ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/visualizations_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/visualizations_section.test.tsx index 8133dfa528526..54aa5dfe74f84 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/visualizations_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/visualizations_section.test.tsx @@ -15,8 +15,7 @@ import { mockContextValue } from '../mocks/mock_context'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { RightPanelContext } from '../context'; import { useAlertPrevalenceFromProcessTree } from '../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; jest.mock('../../../../common/containers/alerts/use_alert_prevalence_from_process_tree', () => ({ useAlertPrevalenceFromProcessTree: jest.fn(), @@ -39,17 +38,13 @@ describe('', () => { }); it('should render visualizations component', () => { - const flyoutContextValue = { - openLeftPanel: jest.fn(), - } as unknown as ExpandableFlyoutContextValue; - const { getByTestId, getAllByRole } = render( - + - + ); diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.stories.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.stories.tsx index 84553f673022f..4d998153cda1b 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.stories.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.stories.tsx @@ -8,28 +8,22 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { EuiFlyout } from '@elastic/eui'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; import { StorybookProviders } from '../../../common/mock/storybook_providers'; import { mockRiskScoreState } from '../../../timelines/components/side_panel/new_user_detail/__mocks__'; import { HostPanelContent } from './content'; import { mockObservedHostData } from '../mocks'; -const flyoutContextValue = { - openLeftPanel: () => window.alert('openLeftPanel called'), - panels: {}, -} as unknown as ExpandableFlyoutContextValue; - const riskScoreData = { ...mockRiskScoreState, data: [] }; storiesOf('Components/HostPanelContent', module) .addDecorator((storyFn) => ( - + {}}> {storyFn()} - + )) .add('default', () => ( diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.stories.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.stories.tsx index 08e6226ee849e..64fa6dcd80913 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.stories.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.stories.tsx @@ -8,8 +8,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { EuiFlyout } from '@elastic/eui'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; import { StorybookProviders } from '../../../common/mock/storybook_providers'; import { mockManagedUserData, @@ -18,21 +17,16 @@ import { import { UserPanelContent } from './content'; import { mockObservedUser } from './mocks'; -const flyoutContextValue = { - openLeftPanel: () => window.alert('openLeftPanel called'), - panels: {}, -} as unknown as ExpandableFlyoutContextValue; - const riskScoreData = { ...mockRiskScoreState, data: [] }; storiesOf('Components/UserPanelContent', module) .addDecorator((storyFn) => ( - + {}}> {storyFn()} - + )) .add('default', () => ( diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.stories.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.stories.tsx index e41bf0c347b45..ec9c4a65089f2 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.stories.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.stories.tsx @@ -7,9 +7,8 @@ import React from 'react'; import type { Story } from '@storybook/react'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { EuiButtonIcon } from '@elastic/eui'; +import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; import { FlyoutNavigation } from './flyout_navigation'; const expandDetails = () => window.alert('expand left panel'); @@ -19,57 +18,45 @@ export default { title: 'Flyout/Navigation', }; -const flyoutContextValue = { - closeLeftPanel: () => window.alert('close left panel'), - panels: {}, -} as unknown as ExpandableFlyoutContextValue; - export const Expand: Story = () => { return ( - + - + ); }; export const Collapse: Story = () => { return ( - + - + ); }; export const CollapsableWithAction: Story = () => { return ( - + } /> - + ); }; export const NonCollapsableWithAction: Story = () => { return ( - + } /> - + ); }; export const Empty: Story = () => { return ( - + - + ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.test.tsx index 7642dfcfd81e3..be12942453269 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.test.tsx @@ -5,10 +5,9 @@ * 2.0. */ +import type { FC, PropsWithChildren } from 'react'; import React from 'react'; import { act, render } from '@testing-library/react'; -import type { ExpandableFlyoutContextValue } from '@kbn/expandable-flyout/src/context'; -import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { TestProviders } from '../../../common/mock'; import { FlyoutNavigation } from './flyout_navigation'; import { @@ -16,23 +15,45 @@ import { EXPAND_DETAILS_BUTTON_TEST_ID, HEADER_ACTIONS_TEST_ID, } from './test_ids'; -import { mockFlyoutContextValue } from '../../document_details/shared/mocks/mock_flyout_context'; +import { + useExpandableFlyoutContext, + type ExpandableFlyoutApi, + ExpandableFlyoutProvider, +} from '@kbn/expandable-flyout'; const expandDetails = jest.fn(); +const ExpandableFlyoutTestProviders: FC> = ({ children }) => { + return ( + + {children} + + ); +}; + +jest.mock('@kbn/expandable-flyout', () => ({ + useExpandableFlyoutContext: jest.fn(), + ExpandableFlyoutProvider: ({ children }: React.PropsWithChildren<{}>) => <>{children}, +})); + +const flyoutContextValue = { + panels: {}, + closeLeftPanel: jest.fn(), +} as unknown as ExpandableFlyoutApi; + describe('', () => { + beforeEach(() => { + jest.mocked(useExpandableFlyoutContext).mockReturnValue(flyoutContextValue); + }); + describe('when flyout is expandable', () => { it('should render expand button', () => { - const flyoutContextValue = { - panels: {}, - } as unknown as ExpandableFlyoutContextValue; + jest.mocked(useExpandableFlyoutContext).mockReturnValue(flyoutContextValue); const { getByTestId, queryByTestId } = render( - - - - - + + + ); expect(getByTestId(EXPAND_DETAILS_BUTTON_TEST_ID)).toBeInTheDocument(); expect(getByTestId(EXPAND_DETAILS_BUTTON_TEST_ID)).toHaveTextContent('Expand details'); @@ -43,19 +64,15 @@ describe('', () => { }); it('should render collapse button', () => { - const flyoutContextValue = { - closeLeftPanel: jest.fn(), - panels: { - left: {}, - }, - } as unknown as ExpandableFlyoutContextValue; + jest.mocked(useExpandableFlyoutContext).mockReturnValue({ + ...flyoutContextValue, + panels: { left: {} }, + } as unknown as ExpandableFlyoutApi); const { getByTestId, queryByTestId } = render( - - - - - + + + ); expect(getByTestId(COLLAPSE_DETAILS_BUTTON_TEST_ID)).toBeInTheDocument(); @@ -69,11 +86,9 @@ describe('', () => { it('should not render expand details button if flyout is not expandable', () => { const { queryByTestId, getByTestId } = render( - - - } /> - - + + } /> + ); expect(getByTestId(HEADER_ACTIONS_TEST_ID)).toBeInTheDocument(); expect(queryByTestId(EXPAND_DETAILS_BUTTON_TEST_ID)).not.toBeInTheDocument(); @@ -82,15 +97,13 @@ describe('', () => { it('should render actions if there are actions available', () => { const { getByTestId } = render( - - - } - /> - - + + } + /> + ); expect(getByTestId(EXPAND_DETAILS_BUTTON_TEST_ID)).toBeInTheDocument(); expect(getByTestId(HEADER_ACTIONS_TEST_ID)).toBeInTheDocument(); @@ -98,11 +111,9 @@ describe('', () => { it('should render empty component if panel is not expandable and no action is available', async () => { const { container } = render( - - - - - + + + ); await act(async () => { expect(container).toBeEmptyDOMElement();