From d7833bd1e46cc5a417ac1c2e5702fb81b29b0d0b Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Thu, 10 Feb 2022 16:54:45 +0100 Subject: [PATCH 01/23] WIP --- x-pack/plugins/cases/common/ui/types.ts | 9 ---- .../cases_context/cases_context_reducer.ts | 52 +++++++++++++++++++ .../public/components/cases_context/index.tsx | 37 ++++++++++--- .../cases_context/use_cases_context_store.tsx | 13 +++++ .../public/methods/get_cases_context.tsx | 34 ++++++++++++ x-pack/plugins/cases/public/mocks.ts | 1 + x-pack/plugins/cases/public/plugin.ts | 2 + x-pack/plugins/cases/public/types.ts | 6 ++- 8 files changed, 138 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts create mode 100644 x-pack/plugins/cases/public/components/cases_context/use_cases_context_store.tsx create mode 100644 x-pack/plugins/cases/public/methods/get_cases_context.tsx diff --git a/x-pack/plugins/cases/common/ui/types.ts b/x-pack/plugins/cases/common/ui/types.ts index 008d4b9245f63..194467631c9ed 100644 --- a/x-pack/plugins/cases/common/ui/types.ts +++ b/x-pack/plugins/cases/common/ui/types.ts @@ -26,15 +26,6 @@ export interface CasesContextFeatures { export type CasesFeatures = Partial; -export interface CasesContextValue { - owner: string[]; - appId: string; - appTitle: string; - userCanCrud: boolean; - basePath: string; - features: CasesContextFeatures; -} - export interface CasesUiConfigType { markdownPlugins: { lens: boolean; diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts new file mode 100644 index 0000000000000..190f3daab34b3 --- /dev/null +++ b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts @@ -0,0 +1,52 @@ +/* + * 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 { CreateCaseFormProps } from '../create/form'; + +export const initialCasesContextState: CasesContextState = { + casesFlyout: { + isFlyoutOpen: false, + }, +}; + +export interface CasesContextState { + casesFlyout: { + isFlyoutOpen: boolean; + props?: CreateCaseFormProps; + }; +} + +export enum CasesContextStoreActionsList { + OPEN_CREATE_CASE_FLYOUT, + CLOSE_CREATE_CASE_FLYOUT, +} +export type CasesContextStoreAction = + | { + type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT; + payload: CreateCaseFormProps; + } + | { type: CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT }; + +export const casesContextReducer: React.Reducer = ( + state: CasesContextState, + action: CasesContextStoreAction +): CasesContextState => { + console.log('I got this action', action); + console.log('the current state', state); + switch (action.type) { + case CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT: { + return { + ...state, + casesFlyout: { + isFlyoutOpen: true, + props: action.payload, + }, + }; + } + } + return state; +}; diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index aceefad97382a..7ace24902343b 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -5,21 +5,37 @@ * 2.0. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useReducer } from 'react'; import { merge } from 'lodash'; -import { CasesContextValue, CasesFeatures } from '../../../common/ui/types'; import { DEFAULT_FEATURES } from '../../../common/constants'; import { DEFAULT_BASE_PATH } from '../../common/navigation'; import { useApplication } from './use_application'; +import { + CasesContextStoreAction, + casesContextReducer, + CasesContextState, + initialCasesContextState, +} from './cases_context_reducer'; +import { CasesContextFeatures, CasesFeatures } from '../../containers/types'; -export const CasesContext = React.createContext(undefined); +export interface CasesContextValue { + owner: string[]; + appId: string; + appTitle: string; + userCanCrud: boolean; + basePath: string; + features: CasesContextFeatures; + state: CasesContextState; + dispatch: (action: CasesContextStoreAction) => void; +} -export interface CasesContextProps - extends Omit { +export interface CasesContextProps extends Pick { basePath?: string; features?: CasesFeatures; } +export const CasesContext = React.createContext(undefined); + export interface CasesContextStateValue extends Omit { appId?: string; appTitle?: string; @@ -30,6 +46,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ value: { owner, userCanCrud, basePath = DEFAULT_BASE_PATH, features = {} }, }) => { const { appId, appTitle } = useApplication(); + const [state, dispatch] = useReducer(casesContextReducer, initialCasesContextState); const [value, setValue] = useState(() => ({ owner, userCanCrud, @@ -39,6 +56,8 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ * of the DEFAULT_FEATURES object */ features: merge({}, DEFAULT_FEATURES, features), + state, + dispatch, })); /** @@ -58,7 +77,10 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ }, [appTitle, appId, userCanCrud]); return isCasesContextValue(value) ? ( - {children} + + {value.state.casesFlyout.isFlyoutOpen ?

{'OPEN THE FLYOUT!'}

: null} + {children} +
) : null; }; CasesProvider.displayName = 'CasesProvider'; @@ -66,3 +88,6 @@ CasesProvider.displayName = 'CasesProvider'; function isCasesContextValue(value: CasesContextStateValue): value is CasesContextValue { return value.appId != null && value.appTitle != null && value.userCanCrud != null; } + +// eslint-disable-next-line import/no-default-export +export default CasesProvider; diff --git a/x-pack/plugins/cases/public/components/cases_context/use_cases_context_store.tsx b/x-pack/plugins/cases/public/components/cases_context/use_cases_context_store.tsx new file mode 100644 index 0000000000000..67cca57d9d243 --- /dev/null +++ b/x-pack/plugins/cases/public/components/cases_context/use_cases_context_store.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCasesContext } from './use_cases_context'; + +export const useCasesContextStore = () => { + const { state, dispatch } = useCasesContext(); + return { state, dispatch }; +}; diff --git a/x-pack/plugins/cases/public/methods/get_cases_context.tsx b/x-pack/plugins/cases/public/methods/get_cases_context.tsx new file mode 100644 index 0000000000000..a2314696773b0 --- /dev/null +++ b/x-pack/plugins/cases/public/methods/get_cases_context.tsx @@ -0,0 +1,34 @@ +/* + * 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 { EuiLoadingSpinner } from '@elastic/eui'; +import React, { lazy, ReactNode, Suspense } from 'react'; +import { CasesContextProps } from '../components/cases_context'; + +export type GetCasesContextProps = CasesContextProps; + +const CasesProviderLazy: React.FC<{ value: GetCasesContextProps }> = lazy( + () => import('../components/cases_context') +); + +const CasesProviderLazyWrapper = ({ + owner, + userCanCrud, + features, + children, +}: GetCasesContextProps & { children: ReactNode }) => { + return ( + }> + {children} + + ); +}; +CasesProviderLazyWrapper.displayName = 'CasesProviderLazyWrapper'; + +export const getCasesContextLazy = () => { + return CasesProviderLazyWrapper; +}; diff --git a/x-pack/plugins/cases/public/mocks.ts b/x-pack/plugins/cases/public/mocks.ts index 6f508d9b6da3b..ca3734db69df8 100644 --- a/x-pack/plugins/cases/public/mocks.ts +++ b/x-pack/plugins/cases/public/mocks.ts @@ -10,6 +10,7 @@ import { CasesUiStart } from './types'; const createStartContract = (): jest.Mocked => ({ canUseCases: jest.fn(), getCases: jest.fn(), + getCasesContext: jest.fn(), getAllCasesSelectorModal: jest.fn(), getCreateCaseFlyout: jest.fn(), getRecentCases: jest.fn(), diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index 70882560edb77..3e7c0e5da9cc3 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -16,6 +16,7 @@ import { canUseCases, } from './methods'; import { CasesUiConfigType } from '../common/ui/types'; +import { getCasesContextLazy } from './methods/get_cases_context'; /** * @public @@ -35,6 +36,7 @@ export class CasesUiPlugin implements Plugin} */ getCases: (props: GetCasesProps) => ReactElement; + getCasesContext: () => ( + props: GetCasesContextProps & { children: ReactNode } + ) => ReactElement; /** * Modal to select a case in a list of all owner cases * @param props GetAllCasesSelectorModalProps From f9de000324e45f02f4a9cc5f68f62875490323c4 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Fri, 11 Feb 2022 16:27:40 +0100 Subject: [PATCH 02/23] WIP2 --- .../cases_context/cases_context_reducer.ts | 12 ++--- .../public/components/cases_context/index.tsx | 6 ++- .../use_cases_add_to_new_case_flyout.tsx | 24 ++++++++++ .../public/methods/get_create_case_flyout.tsx | 8 +++- x-pack/plugins/cases/public/plugin.ts | 6 +++ x-pack/plugins/cases/public/types.ts | 8 ++++ .../components/alerts_table/index.tsx | 45 +++++++++++-------- 7 files changed, 82 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts index 190f3daab34b3..ee98213d09d9a 100644 --- a/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts +++ b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts @@ -5,18 +5,18 @@ * 2.0. */ -import { CreateCaseFormProps } from '../create/form'; +import { CreateCaseFlyoutProps } from '../create/flyout'; export const initialCasesContextState: CasesContextState = { - casesFlyout: { + createCaseFlyout: { isFlyoutOpen: false, }, }; export interface CasesContextState { - casesFlyout: { + createCaseFlyout: { isFlyoutOpen: boolean; - props?: CreateCaseFormProps; + props?: CreateCaseFlyoutProps; }; } @@ -27,7 +27,7 @@ export enum CasesContextStoreActionsList { export type CasesContextStoreAction = | { type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT; - payload: CreateCaseFormProps; + payload: CreateCaseFlyoutProps; } | { type: CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT }; @@ -41,7 +41,7 @@ export const casesContextReducer: React.Reducer = ({ return isCasesContextValue(value) ? ( - {value.state.casesFlyout.isFlyoutOpen ?

{'OPEN THE FLYOUT!'}

: null} + {/* TODO: Move this out */} + {value.state.createCaseFlyout.isFlyoutOpen && value.state.createCaseFlyout.props !== undefined + ? getCreateCaseFlyoutLazyNoProvider(value.state.createCaseFlyout.props) + : null} {children}
) : null; diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx new file mode 100644 index 0000000000000..de74a49d38323 --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx @@ -0,0 +1,24 @@ +/* + * 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 { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer'; +import { useCasesContext } from '../../cases_context/use_cases_context'; +import { CreateCaseFlyoutProps } from './create_case_flyout'; + +export const useCasesAddToNewCasesFlyout = () => { + const context = useCasesContext(); + return { + open: (props: CreateCaseFlyoutProps) => { + context.dispatch({ + type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, + payload: props, + }); + }, + }; +}; + +export type UseCasesAddToNewCasesFlyout = typeof useCasesAddToNewCasesFlyout; diff --git a/x-pack/plugins/cases/public/methods/get_create_case_flyout.tsx b/x-pack/plugins/cases/public/methods/get_create_case_flyout.tsx index 90fbeafaa9ed0..a0453c8fbb47e 100644 --- a/x-pack/plugins/cases/public/methods/get_create_case_flyout.tsx +++ b/x-pack/plugins/cases/public/methods/get_create_case_flyout.tsx @@ -12,7 +12,7 @@ import { CasesProvider, CasesContextProps } from '../components/cases_context'; export type GetCreateCaseFlyoutProps = CreateCaseFlyoutProps & CasesContextProps; -const CreateCaseFlyoutLazy: React.FC = lazy( +export const CreateCaseFlyoutLazy: React.FC = lazy( () => import('../components/create/flyout') ); export const getCreateCaseFlyoutLazy = ({ @@ -35,3 +35,9 @@ export const getCreateCaseFlyoutLazy = ({ ); + +export const getCreateCaseFlyoutLazyNoProvider = (props: CreateCaseFlyoutProps) => ( + }> + + +); diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index 3e7c0e5da9cc3..306ecffa4c89d 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -14,9 +14,11 @@ import { getAllCasesSelectorModalLazy, getCreateCaseFlyoutLazy, canUseCases, + getCreateCaseFlyoutLazyNoProvider, } from './methods'; import { CasesUiConfigType } from '../common/ui/types'; import { getCasesContextLazy } from './methods/get_cases_context'; +import { useCasesAddToNewCasesFlyout } from './components/create/flyout/use_cases_add_to_new_case_flyout'; /** * @public @@ -40,6 +42,10 @@ export class CasesUiPlugin implements Plugin useCasesAddToNewCasesFlyout, + }, }; } diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts index 6ff05cb0618aa..d4934083c7428 100644 --- a/x-pack/plugins/cases/public/types.ts +++ b/x-pack/plugins/cases/public/types.ts @@ -24,6 +24,8 @@ import type { CasesOwners, } from './methods'; import { GetCasesContextProps } from './methods/get_cases_context'; +import { CreateCaseFlyoutProps } from './components/create/flyout'; +import { UseCasesAddToNewCasesFlyout } from './components/create/flyout/use_cases_add_to_new_case_flyout'; export interface SetupPlugins { security: SecurityPluginSetup; @@ -82,10 +84,16 @@ export interface CasesUiStart { * @returns A react component that is a flyout for creating a case */ getCreateCaseFlyout: (props: GetCreateCaseFlyoutProps) => ReactElement; + getCreateCaseFlyoutNoProvider: ( + props: CreateCaseFlyoutProps + ) => ReactElement; /** * Get the recent cases component * @param props GetRecentCasesProps * @returns A react component for showing recent cases */ getRecentCases: (props: GetRecentCasesProps) => ReactElement; + hooks: { + getUseCasesAddToNewCasesFlyout: () => UseCasesAddToNewCasesFlyout; + }; } diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 256a063c44158..8f8ef198ccaa0 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -10,6 +10,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { connect, ConnectedProps, useDispatch } from 'react-redux'; import { Dispatch } from 'redux'; import type { Filter } from '@kbn/es-query'; +import { APP_ID } from '../../../../common/constants'; import { getEsQueryConfig } from '../../../../../../../src/plugins/data/common'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { RowRendererId, TimelineIdLiteral } from '../../../../common/types/timeline'; @@ -24,7 +25,7 @@ import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; import { defaultCellActions } from '../../../common/lib/cell_actions/default_cell_actions'; -import { useKibana } from '../../../common/lib/kibana'; +import { useGetUserCasesPermissions, useKibana } from '../../../common/lib/kibana'; import { inputsModel, inputsSelectors, State } from '../../../common/store'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import * as i18nCommon from '../../../common/translations'; @@ -356,29 +357,35 @@ export const AlertsTableComponent: React.FC = ({ const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []); + // TODO WIP + const casesPermissions = useGetUserCasesPermissions(); + const CasesContext = kibana.services.cases.getCasesContext(); + if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) { return null; } return ( - + + + ); }; From fe21f1a8a1990754ca7fc3d32ee01be5ecb52af0 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Mon, 14 Feb 2022 13:50:47 +0100 Subject: [PATCH 03/23] Use new cases context hooks to open and close the flyout --- .../cases_context/cases_context_reducer.ts | 16 ++++----- .../public/components/cases_context/index.tsx | 34 +++++++++++-------- .../create/flyout/create_case_flyout.tsx | 10 +++--- .../use_cases_add_to_new_case_flyout.tsx | 29 ++++++++++++---- .../cases/public/components/create/form.tsx | 1 + x-pack/plugins/cases/public/index.tsx | 2 ++ 6 files changed, 56 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts index ee98213d09d9a..26f7940b0b2d6 100644 --- a/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts +++ b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts @@ -35,18 +35,14 @@ export const casesContextReducer: React.Reducer { - console.log('I got this action', action); - console.log('the current state', state); switch (action.type) { case CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT: { - return { - ...state, - createCaseFlyout: { - isFlyoutOpen: true, - props: action.payload, - }, - }; + return { ...state, createCaseFlyout: { isFlyoutOpen: true, props: action.payload } }; } + case CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT: { + return { ...state, createCaseFlyout: { isFlyoutOpen: false } }; + } + default: + return state; } - return state; }; diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index c32d9641bd41a..9f9c6e7f3e74e 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useEffect, useReducer } from 'react'; +import React, { useState, useEffect, useReducer, useMemo } from 'react'; import { merge } from 'lodash'; import { DEFAULT_FEATURES } from '../../../common/constants'; import { DEFAULT_BASE_PATH } from '../../common/navigation'; @@ -48,18 +48,18 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ }) => { const { appId, appTitle } = useApplication(); const [state, dispatch] = useReducer(casesContextReducer, initialCasesContextState); - const [value, setValue] = useState(() => ({ - owner, - userCanCrud, - basePath, - /** - * The empty object at the beginning avoids the mutation - * of the DEFAULT_FEATURES object - */ - features: merge({}, DEFAULT_FEATURES, features), - state, - dispatch, - })); + const [baseValue, setBaseValue] = useState>( + () => ({ + owner, + userCanCrud, + basePath, + /** + * The empty object at the beginning avoids the mutation + * of the DEFAULT_FEATURES object + */ + features: merge({}, DEFAULT_FEATURES, features), + }) + ); /** * `userCanCrud` prop may change by the parent plugin. @@ -68,7 +68,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ */ useEffect(() => { if (appId && appTitle) { - setValue((prev) => ({ + setBaseValue((prev) => ({ ...prev, appId, appTitle, @@ -77,9 +77,13 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ } }, [appTitle, appId, userCanCrud]); + const value = useMemo(() => { + return { ...baseValue, state, dispatch }; + }, [baseValue, state]); + return isCasesContextValue(value) ? ( - {/* TODO: Move this out */} + {/* TODO: Move this out to a separate component */} {value.state.createCaseFlyout.isFlyoutOpen && value.state.createCaseFlyout.props !== undefined ? getCreateCaseFlyoutLazyNoProvider(value.state.createCaseFlyout.props) : null} diff --git a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx index 0097df1587a73..c40dfc98513d8 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx @@ -16,8 +16,8 @@ import { UsePostComment } from '../../../containers/use_post_comment'; export interface CreateCaseFlyoutProps { afterCaseCreated?: (theCase: Case, postComment: UsePostComment['postComment']) => Promise; - onClose: () => void; - onSuccess: (theCase: Case) => Promise; + onClose?: () => void; + onSuccess?: (theCase: Case) => Promise; attachments?: CreateCaseAttachment; } @@ -66,6 +66,8 @@ const FormWrapper = styled.div` export const CreateCaseFlyout = React.memo( ({ afterCaseCreated, onClose, onSuccess, attachments }) => { + const handleCancel = onClose || function () {}; + const handleOnSuccess = onSuccess || async function () {}; return ( <> @@ -85,8 +87,8 @@ export const CreateCaseFlyout = React.memo( diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx index de74a49d38323..308a85978556f 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx @@ -9,15 +9,30 @@ import { CasesContextStoreActionsList } from '../../cases_context/cases_context_ import { useCasesContext } from '../../cases_context/use_cases_context'; import { CreateCaseFlyoutProps } from './create_case_flyout'; -export const useCasesAddToNewCasesFlyout = () => { +export const useCasesAddToNewCasesFlyout = (props: CreateCaseFlyoutProps) => { const context = useCasesContext(); + const closeFlyout = () => { + context.dispatch({ + type: CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT, + }); + }; + const openFlyout = () => { + context.dispatch({ + type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, + payload: { + ...props, + onClose: () => { + closeFlyout(); + if (props.onClose) { + props.onClose(); + } + }, + }, + }); + }; return { - open: (props: CreateCaseFlyoutProps) => { - context.dispatch({ - type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, - payload: props, - }); - }, + open: openFlyout, + close: closeFlyout, }; }; diff --git a/x-pack/plugins/cases/public/components/create/form.tsx b/x-pack/plugins/cases/public/components/create/form.tsx index c4784f9a891b1..c4646ff7f7c02 100644 --- a/x-pack/plugins/cases/public/components/create/form.tsx +++ b/x-pack/plugins/cases/public/components/create/form.tsx @@ -57,6 +57,7 @@ const MySpinner = styled(EuiLoadingSpinner)` `; export type SupportedCreateCaseAttachment = CommentRequestAlertType | CommentRequestUserType; export type CreateCaseAttachment = SupportedCreateCaseAttachment[]; +export type CaseAttachments = SupportedCreateCaseAttachment[]; export interface CreateCaseFormFieldsProps { connectors: ActionConnector[]; diff --git a/x-pack/plugins/cases/public/index.tsx b/x-pack/plugins/cases/public/index.tsx index 79eefba78a488..be23b9a46893b 100644 --- a/x-pack/plugins/cases/public/index.tsx +++ b/x-pack/plugins/cases/public/index.tsx @@ -19,6 +19,8 @@ export type { GetCreateCaseFlyoutProps } from './methods/get_create_case_flyout' export type { GetAllCasesSelectorModalProps } from './methods/get_all_cases_selector_modal'; export type { GetRecentCasesProps } from './methods/get_recent_cases'; +export type { CaseAttachments } from './components/create/form'; + export type { ICasesDeepLinkId } from './common/navigation'; export { getCasesDeepLinks, From 55e496a8dd6f6b360fba02ef892cdeccd12b25d6 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Mon, 14 Feb 2022 13:51:36 +0100 Subject: [PATCH 04/23] Update timelines to use new hooks --- x-pack/plugins/cases/public/mocks.ts | 4 ++++ x-pack/plugins/cases/public/plugin.ts | 2 +- x-pack/plugins/cases/public/types.ts | 2 +- .../timeline/cases/add_to_new_case_button.tsx | 18 ++++++++++++-- .../timelines/public/hooks/use_add_to_case.ts | 24 +++++++++++++++++-- 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/cases/public/mocks.ts b/x-pack/plugins/cases/public/mocks.ts index ca3734db69df8..0509886c40e67 100644 --- a/x-pack/plugins/cases/public/mocks.ts +++ b/x-pack/plugins/cases/public/mocks.ts @@ -14,6 +14,10 @@ const createStartContract = (): jest.Mocked => ({ getAllCasesSelectorModal: jest.fn(), getCreateCaseFlyout: jest.fn(), getRecentCases: jest.fn(), + getCreateCaseFlyoutNoProvider: jest.fn(), + hooks: { + getUseCasesAddToNewCasesFlyout: jest.fn(), + }, }); export const casesPluginMock = { diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index 306ecffa4c89d..f12723b4feb1f 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -44,7 +44,7 @@ export class CasesUiPlugin implements Plugin useCasesAddToNewCasesFlyout, + getUseCasesAddToNewCasesFlyout: useCasesAddToNewCasesFlyout, }, }; } diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts index d4934083c7428..a45d8254c7fa8 100644 --- a/x-pack/plugins/cases/public/types.ts +++ b/x-pack/plugins/cases/public/types.ts @@ -94,6 +94,6 @@ export interface CasesUiStart { */ getRecentCases: (props: GetRecentCasesProps) => ReactElement; hooks: { - getUseCasesAddToNewCasesFlyout: () => UseCasesAddToNewCasesFlyout; + getUseCasesAddToNewCasesFlyout: UseCasesAddToNewCasesFlyout; }; } diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx index a83cca49c1fd7..005173266675e 100644 --- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx +++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx @@ -8,6 +8,8 @@ import React, { memo } from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { TimelinesStartServices } from '../../../../types'; import { useAddToCase } from '../../../../hooks/use_add_to_case'; import { AddToCaseActionProps } from './add_to_case_action'; import * as i18n from './translations'; @@ -25,7 +27,7 @@ const AddToNewCaseButtonComponent: React.FC = ({ owner, onClose, }) => { - const { addNewCaseClick, isDisabled, userCanCrud } = useAddToCase({ + const { isDisabled, userCanCrud, caseAttachments } = useAddToCase({ event, useInsertTimeline, casePermissions, @@ -33,6 +35,18 @@ const AddToNewCaseButtonComponent: React.FC = ({ owner, onClose, }); + const { cases } = useKibana().services; + const createCaseFlyout = cases.hooks.getUseCasesAddToNewCasesFlyout({ + attachments: caseAttachments, + }); + + const handleClick = () => { + // close the popover + if (onClose) { + onClose(); + } + createCaseFlyout.open(); + }; return ( <> @@ -40,7 +54,7 @@ const AddToNewCaseButtonComponent: React.FC = ({ void; @@ -32,6 +32,7 @@ interface UseAddToCase { closePopover: () => void; isPopoverOpen: boolean; isCreateCaseFlyoutOpen: boolean; + caseAttachments?: CaseAttachments; } export const useAddToCase = ({ @@ -39,6 +40,7 @@ export const useAddToCase = ({ casePermissions, appId, onClose, + owner, }: AddToCaseActionProps): UseAddToCase => { const eventId = event?.ecs._id ?? ''; const dispatch = useDispatch(); @@ -109,6 +111,23 @@ export const useAddToCase = ({ }, [onViewCaseClick, toasts, dispatch, eventId] ); + const caseAttachments: CaseAttachments = useMemo(() => { + const eventIndex = event?.ecs._index ?? ''; + const { ruleId, ruleName } = normalizedEventFields(event); + const attachments = [ + { + alertId: eventId, + index: eventIndex ?? '', + rule: { + id: ruleId, + name: ruleName, + }, + owner, + type: CommentType.alert as const, + }, + ]; + return attachments; + }, [event, eventId, owner]); const onCaseClicked = useCallback( (theCase?: Case) => { @@ -140,6 +159,7 @@ export const useAddToCase = ({ } }, [onClose, closePopover, dispatch, eventId]); return { + caseAttachments, addNewCaseClick, addExistingCaseClick, onCaseClicked, From e137ba68a08f69efe3f5d91d7f2d4f9cda68f855 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Mon, 14 Feb 2022 14:06:30 +0100 Subject: [PATCH 05/23] CLose flyout on create success --- .../create/flyout/use_cases_add_to_new_case_flyout.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx index 308a85978556f..acb5d37779841 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx @@ -21,10 +21,10 @@ export const useCasesAddToNewCasesFlyout = (props: CreateCaseFlyoutProps) => { type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, payload: { ...props, - onClose: () => { + afterCaseCreated: async (...args) => { closeFlyout(); - if (props.onClose) { - props.onClose(); + if (props.afterCaseCreated) { + return props.afterCaseCreated(...args); } }, }, From 4c580b9ad84c69ac985210b5a77d48af0c94df54 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Mon, 14 Feb 2022 14:11:30 +0100 Subject: [PATCH 06/23] Add back sucess toast --- .../create/flyout/use_cases_add_to_new_case_flyout.tsx | 6 ++++++ .../actions/timeline/cases/add_to_new_case_button.tsx | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx index acb5d37779841..b7d91c8d1771a 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx @@ -21,6 +21,12 @@ export const useCasesAddToNewCasesFlyout = (props: CreateCaseFlyoutProps) => { type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, payload: { ...props, + onClose: () => { + closeFlyout(); + if (props.onClose) { + return props.onClose(); + } + }, afterCaseCreated: async (...args) => { closeFlyout(); if (props.afterCaseCreated) { diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx index 005173266675e..89b104b7e3084 100644 --- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx +++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx @@ -27,7 +27,7 @@ const AddToNewCaseButtonComponent: React.FC = ({ owner, onClose, }) => { - const { isDisabled, userCanCrud, caseAttachments } = useAddToCase({ + const { isDisabled, userCanCrud, caseAttachments, onCaseSuccess, onCaseCreated } = useAddToCase({ event, useInsertTimeline, casePermissions, @@ -38,6 +38,8 @@ const AddToNewCaseButtonComponent: React.FC = ({ const { cases } = useKibana().services; const createCaseFlyout = cases.hooks.getUseCasesAddToNewCasesFlyout({ attachments: caseAttachments, + afterCaseCreated: onCaseCreated, + onSuccess: onCaseSuccess, }); const handleClick = () => { From c6c89aa1cbdcaac9e9f3fbfb4507955b65492a7a Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Mon, 14 Feb 2022 14:26:48 +0100 Subject: [PATCH 07/23] Move code to a dedicated component --- .../cases_context/cases_context_ui.tsx | 21 +++++++++++++++++++ .../public/components/cases_context/index.tsx | 6 ++---- 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/cases/public/components/cases_context/cases_context_ui.tsx diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.tsx b/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.tsx new file mode 100644 index 0000000000000..865902cdfdd34 --- /dev/null +++ b/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { getCreateCaseFlyoutLazyNoProvider } from '../../methods'; +import { CasesContextState } from './cases_context_reducer'; + +export const CasesContextUI = ({ state }: { state: CasesContextState }) => { + return ( + <> + {state.createCaseFlyout.isFlyoutOpen && state.createCaseFlyout.props !== undefined + ? getCreateCaseFlyoutLazyNoProvider(state.createCaseFlyout.props) + : null} + + ); +}; +CasesContextUI.displayName = 'CasesContextUi'; diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index 9f9c6e7f3e74e..3d67390aac789 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -18,6 +18,7 @@ import { } from './cases_context_reducer'; import { CasesContextFeatures, CasesFeatures } from '../../containers/types'; import { getCreateCaseFlyoutLazyNoProvider } from '../../methods'; +import { CasesContextUI } from './cases_context_ui'; export interface CasesContextValue { owner: string[]; @@ -83,10 +84,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ return isCasesContextValue(value) ? ( - {/* TODO: Move this out to a separate component */} - {value.state.createCaseFlyout.isFlyoutOpen && value.state.createCaseFlyout.props !== undefined - ? getCreateCaseFlyoutLazyNoProvider(value.state.createCaseFlyout.props) - : null} + {children} ) : null; From 6392bf179e7ea0f5b390614a7c0e6b6f069c0acc Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Mon, 14 Feb 2022 15:08:28 +0100 Subject: [PATCH 08/23] Add CasesContext to observability --- .../containers/alerts_page/alerts_page.tsx | 27 ++++++++++++++----- .../components/alerts_table/index.tsx | 1 - 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx index dc7eab6b0ca40..0ecd9e3db996a 100644 --- a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx @@ -13,7 +13,10 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; import { ALERT_STATUS, AlertStatus } from '@kbn/rule-data-utils'; +import { observabilityFeatureId } from '../../../../../common'; +import { useGetUserCasesPermissions } from '../../../../hooks/use_get_user_cases_permissions'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { loadAlertAggregations as loadRuleAggregations } from '../../../../../../../plugins/triggers_actions_ui/public'; import { AlertStatusFilterButton } from '../../../../../common/typings'; import { ParsedTechnicalFields } from '../../../../../../rule_registry/common/parse_technical_fields'; @@ -35,6 +38,7 @@ import { } from '../state_container'; import './styles.scss'; import { AlertsStatusFilter, AlertsDisclaimer, AlertsSearchBar } from '../../components'; +import { ObservabilityAppServices } from '../../../../application/types'; interface RuleStatsState { total: number; @@ -228,6 +232,10 @@ function AlertsPage() { // If there is any data, set hasData to true otherwise we need to wait till all the data is loaded before setting hasData to true or false; undefined indicates the data is still loading. const hasData = hasAnyData === true || (isAllRequestsComplete === false ? undefined : false); + const kibana = useKibana(); + const CasesContext = kibana.services.cases.getCasesContext(); + const userPermissions = useGetUserCasesPermissions(); + if (!hasAnyData && !isAllRequestsComplete) { return ; } @@ -322,13 +330,18 @@ function AlertsPage() { - + + + diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 8f8ef198ccaa0..1499e803fdf37 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -357,7 +357,6 @@ export const AlertsTableComponent: React.FC = ({ const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []); - // TODO WIP const casesPermissions = useGetUserCasesPermissions(); const CasesContext = kibana.services.cases.getCasesContext(); From 205728ed716768182155028c5cb703ff5dccba29 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Mon, 14 Feb 2022 15:09:05 +0100 Subject: [PATCH 09/23] Remove dependency --- x-pack/plugins/cases/public/components/cases_context/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index 3d67390aac789..33cd3dbbad22a 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -17,7 +17,6 @@ import { initialCasesContextState, } from './cases_context_reducer'; import { CasesContextFeatures, CasesFeatures } from '../../containers/types'; -import { getCreateCaseFlyoutLazyNoProvider } from '../../methods'; import { CasesContextUI } from './cases_context_ui'; export interface CasesContextValue { From 044279c94914721e032d5579d84c7eec96e3541f Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Mon, 14 Feb 2022 15:14:08 +0100 Subject: [PATCH 10/23] Small refactor --- .../cases_context/cases_context_reducer.ts | 10 ++++++---- .../cases/public/components/cases_context/index.tsx | 2 +- .../cases_context/use_cases_context_store.tsx | 13 ------------- 3 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 x-pack/plugins/cases/public/components/cases_context/use_cases_context_store.tsx diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts index 26f7940b0b2d6..9cd239f2da1b2 100644 --- a/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts +++ b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts @@ -7,10 +7,12 @@ import { CreateCaseFlyoutProps } from '../create/flyout'; -export const initialCasesContextState: CasesContextState = { - createCaseFlyout: { - isFlyoutOpen: false, - }, +export const initialCasesContextState = (): CasesContextState => { + return { + createCaseFlyout: { + isFlyoutOpen: false, + }, + }; }; export interface CasesContextState { diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index 33cd3dbbad22a..2689335aaefc6 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -47,7 +47,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ value: { owner, userCanCrud, basePath = DEFAULT_BASE_PATH, features = {} }, }) => { const { appId, appTitle } = useApplication(); - const [state, dispatch] = useReducer(casesContextReducer, initialCasesContextState); + const [state, dispatch] = useReducer(casesContextReducer, initialCasesContextState()); const [baseValue, setBaseValue] = useState>( () => ({ owner, diff --git a/x-pack/plugins/cases/public/components/cases_context/use_cases_context_store.tsx b/x-pack/plugins/cases/public/components/cases_context/use_cases_context_store.tsx deleted file mode 100644 index 67cca57d9d243..0000000000000 --- a/x-pack/plugins/cases/public/components/cases_context/use_cases_context_store.tsx +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useCasesContext } from './use_cases_context'; - -export const useCasesContextStore = () => { - const { state, dispatch } = useCasesContext(); - return { state, dispatch }; -}; From 04988d6be009d7f78b026bc3d4aa18883d09bee3 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Mon, 14 Feb 2022 15:34:01 +0100 Subject: [PATCH 11/23] Use observabilityAppId instead of observabilityFeatureId for buttons --- .../containers/alerts_table_t_grid/alerts_table_t_grid.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx index d419fbee1d34e..20b86fed197f8 100644 --- a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx @@ -189,14 +189,14 @@ function ObservabilityActions({ timelines.getAddToExistingCaseButton({ event, casePermissions, - appId: observabilityFeatureId, + appId: observabilityAppId, owner: observabilityFeatureId, onClose: afterCaseSelection, }), timelines.getAddToNewCaseButton({ event, casePermissions, - appId: observabilityFeatureId, + appId: observabilityAppId, owner: observabilityFeatureId, onClose: afterCaseSelection, }), From 416d81b88a223b2d8e14b84e21203be1f1321462 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Tue, 15 Feb 2022 12:57:49 +0100 Subject: [PATCH 12/23] Add CasesContext to timetable --- .../components/timeline/body/index.tsx | 105 ++++++++++-------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 7257d4246f6fe..a9d0028f6d9db 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -11,6 +11,8 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { connect, ConnectedProps, useDispatch } from 'react-redux'; import deepEqual from 'fast-deep-equal'; +import { APP_ID } from '../../../../../common/constants'; +import { useGetUserCasesPermissions, useKibana } from '../../../../common/lib/kibana'; import { FIRST_ARIA_INDEX, ARIA_COLINDEX_ATTRIBUTE, @@ -225,60 +227,65 @@ export const BodyComponent = React.memo( }, [columnHeaders.length, containerRef, data.length] ); + const kibana = useKibana(); + const casesPermissions = useGetUserCasesPermissions(); + const CasesContext = kibana.services.cases.getCasesContext(); return ( <> - - + + + - - + + + From 66a0a0da56b7558f36b0a7bd50939b93b2b364fc Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Tue, 15 Feb 2022 13:47:50 +0100 Subject: [PATCH 13/23] Fix detection engine test cases --- .../security_solution/public/common/lib/kibana/hooks.ts | 4 ++-- .../pages/detection_engine/detection_engine.test.tsx | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts index 411dd5542038b..9c8747da19934 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts @@ -157,8 +157,8 @@ export const useGetUserCasesPermissions = () => { useEffect(() => { setCasesPermissions({ - crud: !!uiCapabilities[CASES_FEATURE_ID].crud_cases, - read: !!uiCapabilities[CASES_FEATURE_ID].read_cases, + crud: !!uiCapabilities[CASES_FEATURE_ID]?.crud_cases ?? false, + read: !!uiCapabilities[CASES_FEATURE_ID]?.read_cases ?? false, }); }, [uiCapabilities]); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index c5d053c57fc97..c90e25da917f8 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -72,6 +72,9 @@ jest.mock('../../../common/lib/kibana', () => { siem: { crud_alerts: true, read_alerts: true }, }, }, + cases: { + getCasesContext: jest.fn(), + }, uiSettings: { get: jest.fn(), }, From 13caeb678198933cce1ab212aad3a509740b7a00 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Tue, 15 Feb 2022 15:35:19 +0100 Subject: [PATCH 14/23] Fix broken tests --- .../public/common/mock/mock_cases_context.tsx | 12 ++++++++++++ .../pages/detection_engine/detection_engine.test.tsx | 3 ++- .../timeline/query_tab_content/index.test.tsx | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/security_solution/public/common/mock/mock_cases_context.tsx diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_cases_context.tsx b/x-pack/plugins/security_solution/public/common/mock/mock_cases_context.tsx new file mode 100644 index 0000000000000..66c3fc2c932eb --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/mock/mock_cases_context.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; + +export const mockCasesContext: React.FC = (props) => { + return <>{props?.children ?? null}; +}; +mockCasesContext.displayName = 'CasesContextMock'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index c90e25da917f8..4b6cbb6f7e16d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -24,6 +24,7 @@ import { createStore, State } from '../../../common/store'; import { mockHistory, Router } from '../../../common/mock/router'; import { mockTimelines } from '../../../common/mock/mock_timelines_plugin'; import { mockBrowserFields } from '../../../common/containers/source/mock'; +import { mockCasesContext } from '../../../common/mock/mock_cases_context'; // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -73,7 +74,7 @@ jest.mock('../../../common/lib/kibana', () => { }, }, cases: { - getCasesContext: jest.fn(), + getCasesContext: mockCasesContext, }, uiSettings: { get: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index 580f5cf9cc2ae..019bedacbffe8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -26,6 +26,7 @@ import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; import { Direction } from '../../../../../common/search_strategy'; import * as helpers from '../helpers'; +import { mockCasesContext } from '../../../../common/mock/mock_cases_context'; jest.mock('../../../containers/index', () => ({ useTimelineEvents: jest.fn(), @@ -59,6 +60,9 @@ jest.mock('../../../../common/lib/kibana', () => { navigateToApp: jest.fn(), getUrlForApp: jest.fn(), }, + cases: { + getCasesContext: () => mockCasesContext, + }, uiSettings: { get: jest.fn(), }, From 7deb1102cf074b5a43b6979e74b713d4759886a6 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 16 Feb 2022 10:00:20 +0100 Subject: [PATCH 15/23] Fix broken tests --- .../public/timelines/components/timeline/body/index.test.tsx | 4 ++++ .../components/timeline/eql_tab_content/index.test.tsx | 4 ++++ .../components/timeline/pinned_tab_content/index.test.tsx | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index db927e67ccc67..66a140987475c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -24,6 +24,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { timelineActions } from '../../../store/timeline'; import { ColumnHeaderOptions, TimelineTabs } from '../../../../../common/types/timeline'; import { defaultRowRenderers } from './renderers'; +import { mockCasesContext } from '../../../../common/mock/mock_cases_context'; jest.mock('../../../../common/lib/kibana/hooks'); jest.mock('../../../../common/hooks/use_app_toasts'); @@ -40,6 +41,9 @@ jest.mock('../../../../common/lib/kibana', () => { siem: { crud_alerts: true, read_alerts: true }, }, }, + cases: { + getCasesContext: () => mockCasesContext, + }, data: { search: jest.fn(), query: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx index b4bd3aa1f0ae2..43622b7e45365 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx @@ -23,6 +23,7 @@ import { useTimelineEventsDetails } from '../../../containers/details/index'; import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; import { useDraggableKeyboardWrapper as mockUseDraggableKeyboardWrapper } from '../../../../../../timelines/public/components'; +import { mockCasesContext } from '../../../../common/mock/mock_cases_context'; jest.mock('../../../containers/index', () => ({ useTimelineEvents: jest.fn(), @@ -56,6 +57,9 @@ jest.mock('../../../../common/lib/kibana', () => { navigateToApp: jest.fn(), getUrlForApp: jest.fn(), }, + cases: { + getCasesContext: () => mockCasesContext, + }, docLinks: { links: { query: { eql: 'url-eql_doc' } } }, uiSettings: { get: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx index 8707bb33da08c..ffe50f935b9fe 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx @@ -24,6 +24,7 @@ import { mockSourcererScope } from '../../../../common/containers/sourcerer/mock import { PinnedTabContentComponent, Props as PinnedTabContentComponentProps } from '.'; import { Direction } from '../../../../../common/search_strategy'; import { useDraggableKeyboardWrapper as mockUseDraggableKeyboardWrapper } from '../../../../../../timelines/public/components'; +import { mockCasesContext } from '../../../../common/mock/mock_cases_context'; jest.mock('../../../containers/index', () => ({ useTimelineEvents: jest.fn(), @@ -51,6 +52,9 @@ jest.mock('../../../../common/lib/kibana', () => { navigateToApp: jest.fn(), getUrlForApp: jest.fn(), }, + cases: { + getCasesContext: () => mockCasesContext, + }, uiSettings: { get: jest.fn(), }, From 6cf2996b26a04187195b817b3c289291ac509ac2 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 16 Feb 2022 10:46:44 +0100 Subject: [PATCH 16/23] Rename hook --- .../create/flyout/use_cases_add_to_new_case_flyout.tsx | 4 ++-- x-pack/plugins/cases/public/mocks.ts | 2 +- x-pack/plugins/cases/public/plugin.ts | 4 ++-- x-pack/plugins/cases/public/types.ts | 4 ++-- .../actions/timeline/cases/add_to_new_case_button.tsx | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx index b7d91c8d1771a..97989f35f584c 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx @@ -9,7 +9,7 @@ import { CasesContextStoreActionsList } from '../../cases_context/cases_context_ import { useCasesContext } from '../../cases_context/use_cases_context'; import { CreateCaseFlyoutProps } from './create_case_flyout'; -export const useCasesAddToNewCasesFlyout = (props: CreateCaseFlyoutProps) => { +export const useCasesAddToNewCaseFlyout = (props: CreateCaseFlyoutProps) => { const context = useCasesContext(); const closeFlyout = () => { context.dispatch({ @@ -42,4 +42,4 @@ export const useCasesAddToNewCasesFlyout = (props: CreateCaseFlyoutProps) => { }; }; -export type UseCasesAddToNewCasesFlyout = typeof useCasesAddToNewCasesFlyout; +export type UseCasesAddToNewCaseFlyout = typeof useCasesAddToNewCaseFlyout; diff --git a/x-pack/plugins/cases/public/mocks.ts b/x-pack/plugins/cases/public/mocks.ts index 0509886c40e67..7c89bb1ddc2f9 100644 --- a/x-pack/plugins/cases/public/mocks.ts +++ b/x-pack/plugins/cases/public/mocks.ts @@ -16,7 +16,7 @@ const createStartContract = (): jest.Mocked => ({ getRecentCases: jest.fn(), getCreateCaseFlyoutNoProvider: jest.fn(), hooks: { - getUseCasesAddToNewCasesFlyout: jest.fn(), + getUseCasesAddToNewCaseFlyout: jest.fn(), }, }); diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index f12723b4feb1f..acf51c8380f65 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -18,7 +18,7 @@ import { } from './methods'; import { CasesUiConfigType } from '../common/ui/types'; import { getCasesContextLazy } from './methods/get_cases_context'; -import { useCasesAddToNewCasesFlyout } from './components/create/flyout/use_cases_add_to_new_case_flyout'; +import { useCasesAddToNewCaseFlyout } from './components/create/flyout/use_cases_add_to_new_case_flyout'; /** * @public @@ -44,7 +44,7 @@ export class CasesUiPlugin implements Plugin ReactElement; hooks: { - getUseCasesAddToNewCasesFlyout: UseCasesAddToNewCasesFlyout; + getUseCasesAddToNewCaseFlyout: UseCasesAddToNewCaseFlyout; }; } diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx index 89b104b7e3084..18ddda0791c57 100644 --- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx +++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_new_case_button.tsx @@ -36,7 +36,7 @@ const AddToNewCaseButtonComponent: React.FC = ({ onClose, }); const { cases } = useKibana().services; - const createCaseFlyout = cases.hooks.getUseCasesAddToNewCasesFlyout({ + const createCaseFlyout = cases.hooks.getUseCasesAddToNewCaseFlyout({ attachments: caseAttachments, afterCaseCreated: onCaseCreated, onSuccess: onCaseSuccess, From 38b7f50434f21fcad966e337ebae47c94291c673 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 16 Feb 2022 11:34:52 +0100 Subject: [PATCH 17/23] Add test cases for cases context ui --- .../cases_context/cases_context_ui.test.tsx | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 x-pack/plugins/cases/public/components/cases_context/cases_context_ui.test.tsx diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.test.tsx b/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.test.tsx new file mode 100644 index 0000000000000..566d8e2033a36 --- /dev/null +++ b/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.test.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { AppMockRenderer, createAppMockRenderer } from '../../common/mock'; +import { getCreateCaseFlyoutLazyNoProvider } from '../../methods/get_create_case_flyout'; +import { CasesContextUI } from './cases_context_ui'; + +jest.mock('../../methods/get_create_case_flyout'); + +const getCreateCaseFlyoutLazyNoProviderMock = getCreateCaseFlyoutLazyNoProvider as jest.Mock; + +describe('Cases context UI', () => { + let appMock: AppMockRenderer; + + beforeEach(() => { + appMock = createAppMockRenderer(); + getCreateCaseFlyoutLazyNoProviderMock.mockClear(); + }); + + describe('create case flyout', () => { + it('should render the create case flyout when isFlyoutOpen is true', async () => { + const state = { + createCaseFlyout: { + isFlyoutOpen: true, + props: { + attachments: [], + }, + }, + }; + appMock.render(); + expect(getCreateCaseFlyoutLazyNoProviderMock).toHaveBeenCalledWith({ attachments: [] }); + }); + it('should not render the create case flyout when isFlyoutOpen is true', async () => { + const state = { + createCaseFlyout: { + isFlyoutOpen: false, + }, + }; + appMock.render(); + expect(getCreateCaseFlyoutLazyNoProviderMock).not.toHaveBeenCalled(); + }); + }); +}); From a256d8f473aa503ce49d71d272e901ceb76c9e76 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 16 Feb 2022 13:19:37 +0100 Subject: [PATCH 18/23] Add test for new hook --- .../use_cases_add_to_new_case_flyout.test.tsx | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx new file mode 100644 index 0000000000000..0aa2346686dfb --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx @@ -0,0 +1,82 @@ +/* + * 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. + */ + +/* eslint-disable react/display-name */ + +import { renderHook } from '@testing-library/react-hooks'; +import React from 'react'; +import { CasesContext } from '../../cases_context'; +import { + CasesContextStoreActionsList, + initialCasesContextState, +} from '../../cases_context/cases_context_reducer'; +import { useCasesAddToNewCaseFlyout } from './use_cases_add_to_new_case_flyout'; + +describe('use cases add to new case flyout hook', () => { + const dispatch = jest.fn(); + let wrapper: React.FC; + beforeEach(() => { + dispatch.mockReset(); + wrapper = ({ children }) => { + return ( + + {children} + + ); + }; + }); + + it('should throw if called outside of a cases context', () => { + const { result } = renderHook(() => { + useCasesAddToNewCaseFlyout({}); + }); + expect(result.error?.message).toContain( + 'useCasesContext must be used within a CasesProvider and have a defined value' + ); + }); + + it('should dispatch the open action when invoked', () => { + const { result } = renderHook( + () => { + return useCasesAddToNewCaseFlyout({}); + }, + { wrapper } + ); + result.current.open(); + expect(dispatch).toHaveBeenCalledWith( + expect.objectContaining({ + type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, + }) + ); + }); + + it('should dispatch the close action when invoked', () => { + const { result } = renderHook( + () => { + return useCasesAddToNewCaseFlyout({}); + }, + { wrapper } + ); + result.current.close(); + expect(dispatch).toHaveBeenCalledWith( + expect.objectContaining({ + type: CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT, + }) + ); + }); +}); From ff6428642cb371a66ae67e217e83d1e98615ec51 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 16 Feb 2022 13:49:23 +0100 Subject: [PATCH 19/23] Remove state from the provider context --- .../public/components/cases_context/cases_context_ui.tsx | 4 ++-- .../cases/public/components/cases_context/index.tsx | 8 +++----- .../flyout/use_cases_add_to_new_case_flyout.test.tsx | 6 +----- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.tsx b/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.tsx index 865902cdfdd34..671d8a7e467b1 100644 --- a/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { getCreateCaseFlyoutLazyNoProvider } from '../../methods'; import { CasesContextState } from './cases_context_reducer'; -export const CasesContextUI = ({ state }: { state: CasesContextState }) => { +export const CasesContextUI = React.memo(({ state }: { state: CasesContextState }) => { return ( <> {state.createCaseFlyout.isFlyoutOpen && state.createCaseFlyout.props !== undefined @@ -17,5 +17,5 @@ export const CasesContextUI = ({ state }: { state: CasesContextState }) => { : null} ); -}; +}); CasesContextUI.displayName = 'CasesContextUi'; diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index 2689335aaefc6..e5bf4ea8e5415 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -13,7 +13,6 @@ import { useApplication } from './use_application'; import { CasesContextStoreAction, casesContextReducer, - CasesContextState, initialCasesContextState, } from './cases_context_reducer'; import { CasesContextFeatures, CasesFeatures } from '../../containers/types'; @@ -26,7 +25,6 @@ export interface CasesContextValue { userCanCrud: boolean; basePath: string; features: CasesContextFeatures; - state: CasesContextState; dispatch: (action: CasesContextStoreAction) => void; } @@ -78,12 +76,12 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ }, [appTitle, appId, userCanCrud]); const value = useMemo(() => { - return { ...baseValue, state, dispatch }; - }, [baseValue, state]); + return { ...baseValue, dispatch }; + }, [baseValue]); return isCasesContextValue(value) ? ( - + {children} ) : null; diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx index 0aa2346686dfb..2c3750887cb1d 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx @@ -10,10 +10,7 @@ import { renderHook } from '@testing-library/react-hooks'; import React from 'react'; import { CasesContext } from '../../cases_context'; -import { - CasesContextStoreActionsList, - initialCasesContextState, -} from '../../cases_context/cases_context_reducer'; +import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer'; import { useCasesAddToNewCaseFlyout } from './use_cases_add_to_new_case_flyout'; describe('use cases add to new case flyout hook', () => { @@ -30,7 +27,6 @@ describe('use cases add to new case flyout hook', () => { appId: 'test', appTitle: 'jest', basePath: '/jest', - state: initialCasesContextState(), dispatch, features: { alerts: { sync: true }, metrics: [] }, }} From 9cf248d285910a85f86756b043e6d5b87981ada9 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 16 Feb 2022 14:27:14 +0100 Subject: [PATCH 20/23] Remove basevalue --- .../public/components/cases_context/index.tsx | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index e5bf4ea8e5415..34d2b342c572c 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useEffect, useReducer, useMemo } from 'react'; +import React, { useState, useEffect, useReducer } from 'react'; import { merge } from 'lodash'; import { DEFAULT_FEATURES } from '../../../common/constants'; import { DEFAULT_BASE_PATH } from '../../common/navigation'; @@ -46,18 +46,17 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ }) => { const { appId, appTitle } = useApplication(); const [state, dispatch] = useReducer(casesContextReducer, initialCasesContextState()); - const [baseValue, setBaseValue] = useState>( - () => ({ - owner, - userCanCrud, - basePath, - /** - * The empty object at the beginning avoids the mutation - * of the DEFAULT_FEATURES object - */ - features: merge({}, DEFAULT_FEATURES, features), - }) - ); + const [value, setValue] = useState(() => ({ + owner, + userCanCrud, + basePath, + /** + * The empty object at the beginning avoids the mutation + * of the DEFAULT_FEATURES object + */ + features: merge({}, DEFAULT_FEATURES, features), + dispatch, + })); /** * `userCanCrud` prop may change by the parent plugin. @@ -66,7 +65,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ */ useEffect(() => { if (appId && appTitle) { - setBaseValue((prev) => ({ + setValue((prev) => ({ ...prev, appId, appTitle, @@ -75,10 +74,6 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ } }, [appTitle, appId, userCanCrud]); - const value = useMemo(() => { - return { ...baseValue, dispatch }; - }, [baseValue]); - return isCasesContextValue(value) ? ( From 8b159d2b582f7cf0b411cc61bca61b62428bddc4 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 16 Feb 2022 15:29:16 +0100 Subject: [PATCH 21/23] apply suggested renaming --- .../components/cases_context/cases_context_reducer.ts | 2 +- ...ntext_ui.test.tsx => cases_global_components.test.tsx} | 6 +++--- .../{cases_context_ui.tsx => cases_global_components.tsx} | 4 ++-- .../cases/public/components/cases_context/index.tsx | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) rename x-pack/plugins/cases/public/components/cases_context/{cases_context_ui.test.tsx => cases_global_components.test.tsx} (88%) rename x-pack/plugins/cases/public/components/cases_context/{cases_context_ui.tsx => cases_global_components.tsx} (80%) diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts index 9cd239f2da1b2..f948072323214 100644 --- a/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts +++ b/x-pack/plugins/cases/public/components/cases_context/cases_context_reducer.ts @@ -7,7 +7,7 @@ import { CreateCaseFlyoutProps } from '../create/flyout'; -export const initialCasesContextState = (): CasesContextState => { +export const getInitialCasesContextState = (): CasesContextState => { return { createCaseFlyout: { isFlyoutOpen: false, diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.test.tsx b/x-pack/plugins/cases/public/components/cases_context/cases_global_components.test.tsx similarity index 88% rename from x-pack/plugins/cases/public/components/cases_context/cases_context_ui.test.tsx rename to x-pack/plugins/cases/public/components/cases_context/cases_global_components.test.tsx index 566d8e2033a36..4d9a101887b84 100644 --- a/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.test.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/cases_global_components.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { AppMockRenderer, createAppMockRenderer } from '../../common/mock'; import { getCreateCaseFlyoutLazyNoProvider } from '../../methods/get_create_case_flyout'; -import { CasesContextUI } from './cases_context_ui'; +import { CasesGlobalComponents } from './cases_global_components'; jest.mock('../../methods/get_create_case_flyout'); @@ -32,7 +32,7 @@ describe('Cases context UI', () => { }, }, }; - appMock.render(); + appMock.render(); expect(getCreateCaseFlyoutLazyNoProviderMock).toHaveBeenCalledWith({ attachments: [] }); }); it('should not render the create case flyout when isFlyoutOpen is true', async () => { @@ -41,7 +41,7 @@ describe('Cases context UI', () => { isFlyoutOpen: false, }, }; - appMock.render(); + appMock.render(); expect(getCreateCaseFlyoutLazyNoProviderMock).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.tsx b/x-pack/plugins/cases/public/components/cases_context/cases_global_components.tsx similarity index 80% rename from x-pack/plugins/cases/public/components/cases_context/cases_context_ui.tsx rename to x-pack/plugins/cases/public/components/cases_context/cases_global_components.tsx index 671d8a7e467b1..42ff36e201df4 100644 --- a/x-pack/plugins/cases/public/components/cases_context/cases_context_ui.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/cases_global_components.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { getCreateCaseFlyoutLazyNoProvider } from '../../methods'; import { CasesContextState } from './cases_context_reducer'; -export const CasesContextUI = React.memo(({ state }: { state: CasesContextState }) => { +export const CasesGlobalComponents = React.memo(({ state }: { state: CasesContextState }) => { return ( <> {state.createCaseFlyout.isFlyoutOpen && state.createCaseFlyout.props !== undefined @@ -18,4 +18,4 @@ export const CasesContextUI = React.memo(({ state }: { state: CasesContextState ); }); -CasesContextUI.displayName = 'CasesContextUi'; +CasesGlobalComponents.displayName = 'CasesContextUi'; diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index 34d2b342c572c..763f055dd968b 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -13,10 +13,10 @@ import { useApplication } from './use_application'; import { CasesContextStoreAction, casesContextReducer, - initialCasesContextState, + getInitialCasesContextState, } from './cases_context_reducer'; import { CasesContextFeatures, CasesFeatures } from '../../containers/types'; -import { CasesContextUI } from './cases_context_ui'; +import { CasesGlobalComponents } from './cases_global_components'; export interface CasesContextValue { owner: string[]; @@ -45,7 +45,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ value: { owner, userCanCrud, basePath = DEFAULT_BASE_PATH, features = {} }, }) => { const { appId, appTitle } = useApplication(); - const [state, dispatch] = useReducer(casesContextReducer, initialCasesContextState()); + const [state, dispatch] = useReducer(casesContextReducer, getInitialCasesContextState()); const [value, setValue] = useState(() => ({ owner, userCanCrud, @@ -76,7 +76,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ return isCasesContextValue(value) ? ( - + {children} ) : null; From 1486a88fbc85040e3ad103c7e97947b6a6c77023 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 16 Feb 2022 17:12:37 +0100 Subject: [PATCH 22/23] Add usecallback --- .../flyout/use_cases_add_to_new_case_flyout.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx index 97989f35f584c..e9514ee582d99 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx @@ -5,18 +5,21 @@ * 2.0. */ +import { useCallback } from 'react'; import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer'; import { useCasesContext } from '../../cases_context/use_cases_context'; import { CreateCaseFlyoutProps } from './create_case_flyout'; export const useCasesAddToNewCaseFlyout = (props: CreateCaseFlyoutProps) => { const context = useCasesContext(); - const closeFlyout = () => { + + const closeFlyout = useCallback(() => { context.dispatch({ type: CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT, }); - }; - const openFlyout = () => { + }, [context]); + + const openFlyout = useCallback(() => { context.dispatch({ type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, payload: { @@ -35,7 +38,7 @@ export const useCasesAddToNewCaseFlyout = (props: CreateCaseFlyoutProps) => { }, }, }); - }; + }, [closeFlyout, context, props]); return { open: openFlyout, close: closeFlyout, From a297fbf7c042a3ed00fb7021e1b6fb7ca5b6dd8c Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Thu, 17 Feb 2022 10:28:29 +0100 Subject: [PATCH 23/23] Add reducer types, fix test type, remove redundant check --- .../cases_context/cases_global_components.test.tsx | 2 +- .../plugins/cases/public/components/cases_context/index.tsx | 6 ++++-- .../pages/alerts/containers/alerts_page/alerts_page.tsx | 1 + .../security_solution/public/common/lib/kibana/hooks.ts | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/cases/public/components/cases_context/cases_global_components.test.tsx b/x-pack/plugins/cases/public/components/cases_context/cases_global_components.test.tsx index 4d9a101887b84..53c9129812d8b 100644 --- a/x-pack/plugins/cases/public/components/cases_context/cases_global_components.test.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/cases_global_components.test.tsx @@ -35,7 +35,7 @@ describe('Cases context UI', () => { appMock.render(); expect(getCreateCaseFlyoutLazyNoProviderMock).toHaveBeenCalledWith({ attachments: [] }); }); - it('should not render the create case flyout when isFlyoutOpen is true', async () => { + it('should not render the create case flyout when isFlyoutOpen is false', async () => { const state = { createCaseFlyout: { isFlyoutOpen: false, diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index 763f055dd968b..1f1da31595a04 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useEffect, useReducer } from 'react'; +import React, { useState, useEffect, useReducer, Dispatch } from 'react'; import { merge } from 'lodash'; import { DEFAULT_FEATURES } from '../../../common/constants'; import { DEFAULT_BASE_PATH } from '../../common/navigation'; @@ -18,6 +18,8 @@ import { import { CasesContextFeatures, CasesFeatures } from '../../containers/types'; import { CasesGlobalComponents } from './cases_global_components'; +export type CasesContextValueDispatch = Dispatch; + export interface CasesContextValue { owner: string[]; appId: string; @@ -25,7 +27,7 @@ export interface CasesContextValue { userCanCrud: boolean; basePath: string; features: CasesContextFeatures; - dispatch: (action: CasesContextStoreAction) => void; + dispatch: CasesContextValueDispatch; } export interface CasesContextProps extends Pick { diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx index f17ef20b14c08..e8f79ec4e6e27 100644 --- a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx @@ -333,6 +333,7 @@ function AlertsPage() { { useEffect(() => { setCasesPermissions({ - crud: !!uiCapabilities[CASES_FEATURE_ID]?.crud_cases ?? false, - read: !!uiCapabilities[CASES_FEATURE_ID]?.read_cases ?? false, + crud: !!uiCapabilities[CASES_FEATURE_ID]?.crud_cases, + read: !!uiCapabilities[CASES_FEATURE_ID]?.read_cases, }); }, [uiCapabilities]);