diff --git a/x-pack/plugins/cases/public/common/use_cases_toast.tsx b/x-pack/plugins/cases/public/common/use_cases_toast.tsx index 93905eaf64213..baa15f1b47248 100644 --- a/x-pack/plugins/cases/public/common/use_cases_toast.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_toast.tsx @@ -7,7 +7,7 @@ import type { ErrorToastOptions } from '@kbn/core/public'; import { EuiButtonEmpty, EuiText } from '@elastic/eui'; -import React from 'react'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { isValidOwner } from '../../common/utils/owner'; @@ -124,62 +124,65 @@ export const useCasesToast = () => { const toasts = useToasts(); - return { - showSuccessAttach: ({ - theCase, - attachments, - title, - content, - }: { - theCase: CaseUI; - attachments?: CaseAttachmentsWithoutOwner; - title?: string; - content?: string; - }) => { - const appIdToNavigateTo = isValidOwner(theCase.owner) - ? OWNER_INFO[theCase.owner].appId - : appId; - - const url = getUrlForApp(appIdToNavigateTo, { - deepLinkId: 'cases', - path: generateCaseViewPath({ detailName: theCase.id }), - }); - - const onViewCaseClick = () => { - navigateToUrl(url); - }; - - const renderTitle = getToastTitle({ theCase, title, attachments }); - const renderContent = getToastContent({ theCase, content, attachments }); - - return toasts.addSuccess({ - color: 'success', - iconType: 'check', - title: toMountPoint({renderTitle}), - text: toMountPoint( - - ), - }); - }, - showErrorToast: (error: Error | ServerError, opts?: ErrorToastOptions) => { - if (error.name !== 'AbortError') { - toasts.addError(getError(error), { title: getErrorMessage(error), ...opts }); - } - }, - showSuccessToast: (title: string) => { - toasts.addSuccess({ title, className: 'eui-textBreakWord' }); - }, - showDangerToast: (title: string) => { - toasts.addDanger({ title, className: 'eui-textBreakWord' }); - }, - showInfoToast: (title: string, text?: string) => { - toasts.addInfo({ + return useMemo( + () => ({ + showSuccessAttach: ({ + theCase, + attachments, title, - text, - className: 'eui-textBreakWord', - }); - }, - }; + content, + }: { + theCase: CaseUI; + attachments?: CaseAttachmentsWithoutOwner; + title?: string; + content?: string; + }) => { + const appIdToNavigateTo = isValidOwner(theCase.owner) + ? OWNER_INFO[theCase.owner].appId + : appId; + + const url = getUrlForApp(appIdToNavigateTo, { + deepLinkId: 'cases', + path: generateCaseViewPath({ detailName: theCase.id }), + }); + + const onViewCaseClick = () => { + navigateToUrl(url); + }; + + const renderTitle = getToastTitle({ theCase, title, attachments }); + const renderContent = getToastContent({ theCase, content, attachments }); + + return toasts.addSuccess({ + color: 'success', + iconType: 'check', + title: toMountPoint({renderTitle}), + text: toMountPoint( + + ), + }); + }, + showErrorToast: (error: Error | ServerError, opts?: ErrorToastOptions) => { + if (error.name !== 'AbortError') { + toasts.addError(getError(error), { title: getErrorMessage(error), ...opts }); + } + }, + showSuccessToast: (title: string) => { + toasts.addSuccess({ title, className: 'eui-textBreakWord' }); + }, + showDangerToast: (title: string) => { + toasts.addDanger({ title, className: 'eui-textBreakWord' }); + }, + showInfoToast: (title: string, text?: string) => { + toasts.addInfo({ + title, + text, + className: 'eui-textBreakWord', + }); + }, + }), + [appId, getUrlForApp, navigateToUrl, toasts] + ); }; export const CaseToastSuccessContent = ({ diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.tsx index f311fdad66a47..ea60c43937346 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.tsx @@ -30,16 +30,26 @@ export type AddToExistingCaseModalProps = Omit void; }; -export const useCasesAddToExistingCaseModal = (props: AddToExistingCaseModalProps = {}) => { - const createNewCaseFlyout = useCasesAddToNewCaseFlyout({ - onClose: props.onClose, - onSuccess: (theCase?: CaseUI) => { - if (props.onSuccess && theCase) { - return props.onSuccess(theCase); +export const useCasesAddToExistingCaseModal = ({ + successToaster, + noAttachmentsToaster, + onSuccess, + onClose, + onCreateCaseClicked, +}: AddToExistingCaseModalProps = {}) => { + const handleSuccess = useCallback( + (theCase?: CaseUI) => { + if (onSuccess && theCase) { + return onSuccess(theCase); } }, - toastTitle: props.successToaster?.title, - toastContent: props.successToaster?.content, + [onSuccess] + ); + const { open: openCreateNewCaseFlyout } = useCasesAddToNewCaseFlyout({ + onClose, + onSuccess: handleSuccess, + toastTitle: successToaster?.title, + toastContent: successToaster?.content, }); const { dispatch, appId } = useCasesContext(); @@ -69,15 +79,15 @@ export const useCasesAddToExistingCaseModal = (props: AddToExistingCaseModalProp // the user clicked "create new case" if (theCase === undefined) { closeModal(); - createNewCaseFlyout.open({ attachments }); + openCreateNewCaseFlyout({ attachments }); return; } try { // add attachments to the case if (attachments === undefined || attachments.length === 0) { - const title = props.noAttachmentsToaster?.title ?? NO_ATTACHMENTS_ADDED; - const content = props.noAttachmentsToaster?.content; + const title = noAttachmentsToaster?.title ?? NO_ATTACHMENTS_ADDED; + const content = noAttachmentsToaster?.content; casesToasts.showInfoToast(title, content); return; @@ -91,15 +101,13 @@ export const useCasesAddToExistingCaseModal = (props: AddToExistingCaseModalProp attachments, }); - if (props.onSuccess) { - props.onSuccess(theCase); - } + onSuccess?.(theCase); casesToasts.showSuccessAttach({ theCase, attachments, - title: props.successToaster?.title, - content: props.successToaster?.content, + title: successToaster?.title, + content: successToaster?.content, }); } catch (error) { // error toast is handled @@ -111,8 +119,12 @@ export const useCasesAddToExistingCaseModal = (props: AddToExistingCaseModalProp casesToasts, closeModal, createAttachments, - createNewCaseFlyout, - props, + openCreateNewCaseFlyout, + successToaster?.title, + successToaster?.content, + noAttachmentsToaster?.title, + noAttachmentsToaster?.content, + onSuccess, startTransaction, ] ); @@ -126,22 +138,22 @@ export const useCasesAddToExistingCaseModal = (props: AddToExistingCaseModalProp dispatch({ type: CasesContextStoreActionsList.OPEN_ADD_TO_CASE_MODAL, payload: { - ...props, hiddenStatuses: [CaseStatuses.closed], + onCreateCaseClicked, onRowClick: (theCase?: CaseUI) => { handleOnRowClick(theCase, getAttachments); }, onClose: (theCase?: CaseUI, isCreateCase?: boolean) => { closeModal(); - if (props.onClose) { - return props.onClose(theCase, isCreateCase); + if (onClose) { + return onClose(theCase, isCreateCase); } }, }, }); }, - [closeModal, dispatch, handleOnRowClick, props] + [closeModal, dispatch, handleOnRowClick, onClose, onCreateCaseClicked] ); return { 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 a8e234f9bb96a..e6d5bd2fc2a0b 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 @@ -19,7 +19,15 @@ type AddToNewCaseFlyoutProps = Omit & { toastContent?: string; }; -export const useCasesAddToNewCaseFlyout = (props: AddToNewCaseFlyoutProps = {}) => { +export const useCasesAddToNewCaseFlyout = ({ + initialValue, + toastTitle, + toastContent, + + afterCaseCreated, + onSuccess, + onClose, +}: AddToNewCaseFlyoutProps = {}) => { const { dispatch } = useCasesContext(); const casesToasts = useCasesToast(); @@ -37,13 +45,13 @@ export const useCasesAddToNewCaseFlyout = (props: AddToNewCaseFlyoutProps = {}) dispatch({ type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, payload: { - ...props, + initialValue, attachments, headerContent, onClose: () => { closeFlyout(); - if (props.onClose) { - return props.onClose(); + if (onClose) { + return onClose(); } }, onSuccess: async (theCase: CaseUI) => { @@ -51,24 +59,34 @@ export const useCasesAddToNewCaseFlyout = (props: AddToNewCaseFlyoutProps = {}) casesToasts.showSuccessAttach({ theCase, attachments: attachments ?? [], - title: props.toastTitle, - content: props.toastContent, + title: toastTitle, + content: toastContent, }); } - if (props.onSuccess) { - return props.onSuccess(theCase); + if (onSuccess) { + return onSuccess(theCase); } }, afterCaseCreated: async (...args) => { closeFlyout(); - if (props.afterCaseCreated) { - return props.afterCaseCreated(...args); + if (afterCaseCreated) { + return afterCaseCreated(...args); } }, }, }); }, - [casesToasts, closeFlyout, dispatch, props] + [ + initialValue, + casesToasts, + closeFlyout, + dispatch, + toastTitle, + toastContent, + afterCaseCreated, + onSuccess, + onClose, + ] ); return { open: openFlyout, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts index 4d487dcd9186d..1a61042389911 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts @@ -86,7 +86,7 @@ const ACTION_DEFINITION: Record< export const useActions = ({ attributes, lensMetadata, - extraActions = [], + extraActions, inspectActionProps, timeRange, withActions = DEFAULT_ACTIONS, @@ -186,7 +186,7 @@ export const useActions = ({ canUseEditor() && withActions.includes(VisualizationContextMenuActions.openInLens), order: 0, }), - ...extraActions, + ...(extraActions ?? []), ].map((a, i, totalActions) => { const order = Math.max(totalActions.length - (1 + i), 0); return { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.test.tsx index 2ed014ee69a37..5181d33370afe 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.test.tsx @@ -15,31 +15,15 @@ import { } from '../../../cases_test_utils'; import { AttachmentType } from '@kbn/cases-plugin/common'; -const mockedUseKibana = mockUseKibana(); -const mockGetUseCasesAddToExistingCaseModal = jest.fn(); -const mockCanUseCases = jest.fn(); - -jest.mock('../../lib/kibana', () => { - const original = jest.requireActual('../../lib/kibana'); - - return { - ...original, - useKibana: () => ({ - ...mockedUseKibana, - services: { - ...mockedUseKibana.services, - cases: { - hooks: { - useCasesAddToExistingCaseModal: mockGetUseCasesAddToExistingCaseModal, - }, - helpers: { canUseCases: mockCanUseCases }, - }, - }, - }), - }; -}); +jest.mock('../../lib/kibana'); describe('useAddToExistingCase', () => { + const mockedUseKibana = mockUseKibana(); + const mockCanUseCases = jest.fn(); + const mockUseCasesAddToExistingCaseModal = jest.fn().mockReturnValue({ + open: jest.fn(), + close: jest.fn(), + }); const mockOnAddToCaseClicked = jest.fn(); const timeRange = { from: '2022-03-06T16:00:00.000Z', @@ -48,6 +32,9 @@ describe('useAddToExistingCase', () => { beforeEach(() => { mockCanUseCases.mockReturnValue(allCasesPermissions()); + mockedUseKibana.services.cases.hooks.useCasesAddToExistingCaseModal = + mockUseCasesAddToExistingCaseModal; + mockedUseKibana.services.cases.helpers.canUseCases = mockCanUseCases; }); it('useCasesAddToExistingCaseModal with attachments', () => { @@ -59,7 +46,7 @@ describe('useAddToExistingCase', () => { lensMetadata: undefined, }) ); - expect(mockGetUseCasesAddToExistingCaseModal).toHaveBeenCalledWith({ + expect(mockUseCasesAddToExistingCaseModal).toHaveBeenCalledWith({ onClose: mockOnAddToCaseClicked, successToaster: { title: 'Successfully added visualization to the case', @@ -127,7 +114,7 @@ describe('useAddToExistingCase', () => { description: 'test_description', }; - mockGetUseCasesAddToExistingCaseModal.mockReturnValue({ open: mockOpenCaseModal }); + mockUseCasesAddToExistingCaseModal.mockReturnValue({ open: mockOpenCaseModal }); const { result } = renderHook(() => useAddToExistingCase({ diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.tsx index 1675802b9953e..aa11ced2603a9 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.tsx @@ -40,7 +40,7 @@ export const useAddToExistingCase = ({ ] as CaseAttachmentsWithoutOwner; }, [lensAttributes, lensMetadata, timeRange]); - const selectCaseModal = cases.hooks.useCasesAddToExistingCaseModal({ + const { open: openSelectCaseModal } = cases.hooks.useCasesAddToExistingCaseModal({ onClose: onAddToCaseClicked, successToaster: { title: ADD_TO_CASE_SUCCESS, @@ -51,8 +51,8 @@ export const useAddToExistingCase = ({ if (onAddToCaseClicked) { onAddToCaseClicked(); } - selectCaseModal.open({ getAttachments: () => attachments }); - }, [attachments, onAddToCaseClicked, selectCaseModal]); + openSelectCaseModal({ getAttachments: () => attachments }); + }, [attachments, onAddToCaseClicked, openSelectCaseModal]); return { onAddToExistingCaseClicked, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.test.tsx index 91347dc9fe073..fceff5e6cdaae 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.test.tsx @@ -17,37 +17,26 @@ import { AttachmentType } from '@kbn/cases-plugin/common'; jest.mock('../../lib/kibana/kibana_react'); -const mockedUseKibana = mockUseKibana(); -const mockGetUseCasesAddToNewCaseFlyout = jest.fn(); -const mockCanUseCases = jest.fn(); - -jest.mock('../../lib/kibana', () => { - const original = jest.requireActual('../../lib/kibana'); - - return { - ...original, - useKibana: () => ({ - ...mockedUseKibana, - services: { - ...mockedUseKibana.services, - cases: { - hooks: { - useCasesAddToNewCaseFlyout: mockGetUseCasesAddToNewCaseFlyout, - }, - helpers: { canUseCases: mockCanUseCases }, - }, - }, - }), - }; -}); +jest.mock('../../lib/kibana'); describe('useAddToNewCase', () => { + const mockedUseKibana = mockUseKibana(); + const mockCanUseCases = jest.fn(); + const mockGetUseCasesAddToNewCaseFlyout = jest.fn().mockReturnValue({ + open: jest.fn(), + close: jest.fn(), + }); + const timeRange = { from: '2022-03-06T16:00:00.000Z', to: '2022-03-07T15:59:59.999Z', }; + beforeEach(() => { mockCanUseCases.mockReturnValue(allCasesPermissions()); + mockedUseKibana.services.cases.hooks.useCasesAddToNewCaseFlyout = + mockGetUseCasesAddToNewCaseFlyout; + mockedUseKibana.services.cases.helpers.canUseCases = mockCanUseCases; }); it('useCasesAddToNewCaseFlyout with attachments', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.tsx index aca5ba88dc057..c2ac628000fa7 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.tsx @@ -43,7 +43,7 @@ export const useAddToNewCase = ({ ] as CaseAttachmentsWithoutOwner; }, [lensAttributes, lensMetadata, timeRange]); - const createCaseFlyout = cases.hooks.useCasesAddToNewCaseFlyout({ + const { open: openCreateCaseFlyout } = cases.hooks.useCasesAddToNewCaseFlyout({ toastContent: ADD_TO_CASE_SUCCESS, }); @@ -52,8 +52,8 @@ export const useAddToNewCase = ({ onClick(); } - createCaseFlyout.open({ attachments }); - }, [attachments, createCaseFlyout, onClick]); + openCreateCaseFlyout({ attachments }); + }, [attachments, openCreateCaseFlyout, onClick]); return { onAddToNewCaseClicked,