From 8c8993dda916a78809a41a86050e8d93e6cb639d Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 4 Mar 2021 12:58:16 +0200 Subject: [PATCH] [Security Solution][Case] Fix individual case deletion on case view (#93218) (#93564) # Conflicts: # x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx --- .../cases/components/all_cases/index.test.tsx | 15 +++++--- .../cases/components/all_cases/index.tsx | 30 +++++++--------- .../cases/components/bulk_actions/index.tsx | 15 ++++---- .../case_action_bar/actions.test.tsx | 2 +- .../components/case_action_bar/actions.tsx | 4 ++- .../cases/components/case_view/index.tsx | 36 ++++++++++--------- .../public/cases/containers/mock.ts | 31 ++++++++++++++++ .../public/cases/containers/types.ts | 2 +- .../containers/use_delete_cases.test.tsx | 8 ++++- 9 files changed, 92 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx index 1fbda69d8916c..b4fb090700382 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx @@ -11,7 +11,7 @@ import moment from 'moment-timezone'; import { waitFor } from '@testing-library/react'; import '../../../common/mock/match_media'; import { TestProviders } from '../../../common/mock'; -import { casesStatus, useGetCasesMockState } from '../../containers/mock'; +import { casesStatus, useGetCasesMockState, collectionCase } from '../../containers/mock'; import * as i18n from './translations'; import { CaseStatuses, CaseType } from '../../../../../case/common/api'; @@ -410,7 +410,7 @@ describe('AllCases', () => { useGetCasesMock.mockReturnValue({ ...defaultGetCases, filterOptions: { ...defaultGetCases.filterOptions, status: CaseStatuses.closed }, - selectedCases: useGetCasesMockState.data.cases, + selectedCases: [...useGetCasesMockState.data.cases, collectionCase], }); useDeleteCasesMock @@ -439,9 +439,14 @@ describe('AllCases', () => { ) .last() .simulate('click'); - expect(handleOnDeleteConfirm.mock.calls[0][0]).toStrictEqual( - useGetCasesMockState.data.cases.map(({ id }) => ({ id })) - ); + expect(handleOnDeleteConfirm.mock.calls[0][0]).toStrictEqual([ + ...useGetCasesMockState.data.cases.map(({ id, type, title }) => ({ id, type, title })), + { + id: collectionCase.id, + title: collectionCase.title, + type: collectionCase.type, + }, + ]); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx index 5f0e72564f60e..7b72a2e188903 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx @@ -167,6 +167,7 @@ export const AllCases = React.memo( const [deleteThisCase, setDeleteThisCase] = useState({ title: '', id: '', + type: null, }); const [deleteBulk, setDeleteBulk] = useState([]); const filterRefetch = useRef<() => void>(); @@ -230,10 +231,10 @@ export const AllCases = React.memo( ); const toggleBulkDeleteModal = useCallback( - (caseIds: string[]) => { + (cases: Case[]) => { handleToggleModal(); - if (caseIds.length === 1) { - const singleCase = selectedCases.find((theCase) => theCase.id === caseIds[0]); + if (cases.length === 1) { + const singleCase = cases[0]; if (singleCase) { return setDeleteThisCase({ id: singleCase.id, @@ -242,10 +243,14 @@ export const AllCases = React.memo( }); } } - const convertToDeleteCases: DeleteCase[] = caseIds.map((id) => ({ id })); + const convertToDeleteCases: DeleteCase[] = cases.map(({ id, title, type }) => ({ + id, + title, + type, + })); setDeleteBulk(convertToDeleteCases); }, - [selectedCases, setDeleteBulk, handleToggleModal] + [setDeleteBulk, handleToggleModal] ); const handleUpdateCaseStatus = useCallback( @@ -255,11 +260,6 @@ export const AllCases = React.memo( [selectedCases, updateBulkStatus] ); - const selectedCaseIds = useMemo( - (): string[] => selectedCases.map((caseObj: Case) => caseObj.id), - [selectedCases] - ); - const getBulkItemsPopoverContent = useCallback( (closePopover: () => void) => ( ( caseStatus: filterOptions.status, closePopover, deleteCasesAction: toggleBulkDeleteModal, - selectedCaseIds, + selectedCases, updateCaseStatus: handleUpdateCaseStatus, includeCollections: isSelectedCasesIncludeCollections(selectedCases), })} /> ), - [ - selectedCases, - selectedCaseIds, - filterOptions.status, - toggleBulkDeleteModal, - handleUpdateCaseStatus, - ] + [selectedCases, filterOptions.status, toggleBulkDeleteModal, handleUpdateCaseStatus] ); const handleDispatchUpdate = useCallback( (args: Omit) => { diff --git a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx index 17e196d590418..e443ca77cb755 100644 --- a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx @@ -11,12 +11,13 @@ import { EuiContextMenuItem } from '@elastic/eui'; import { CaseStatuses } from '../../../../../case/common/api'; import { CaseStatusWithAllStatus } from '../status'; import * as i18n from './translations'; +import { Case } from '../../containers/types'; interface GetBulkItems { caseStatus: CaseStatusWithAllStatus; closePopover: () => void; - deleteCasesAction: (cases: string[]) => void; - selectedCaseIds: string[]; + deleteCasesAction: (cases: Case[]) => void; + selectedCases: Case[]; updateCaseStatus: (status: string) => void; includeCollections: boolean; } @@ -25,7 +26,7 @@ export const getBulkItems = ({ caseStatus, closePopover, deleteCasesAction, - selectedCaseIds, + selectedCases, updateCaseStatus, includeCollections, }: GetBulkItems) => { @@ -34,7 +35,7 @@ export const getBulkItems = ({ const openMenuItem = ( { @@ -49,7 +50,7 @@ export const getBulkItems = ({ const closeMenuItem = ( { @@ -80,10 +81,10 @@ export const getBulkItems = ({ data-test-subj="cases-bulk-delete-button" key={i18n.BULK_ACTION_DELETE_SELECTED} icon="trash" - disabled={selectedCaseIds.length === 0} + disabled={selectedCases.length === 0} onClick={() => { closePopover(); - deleteCasesAction(selectedCaseIds); + deleteCasesAction(selectedCases); }} > {i18n.BULK_ACTION_DELETE_SELECTED} diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.test.tsx index 58e0e60160c9c..ba0c725f99460 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.test.tsx @@ -74,7 +74,7 @@ describe('CaseView actions', () => { expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeTruthy(); wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click'); expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([ - { id: basicCase.id, title: basicCase.title }, + { id: basicCase.id, title: basicCase.title, type: 'individual' }, ]); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.tsx b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.tsx index 80047d7e573ba..74d2a40f1ceb9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.tsx @@ -42,7 +42,9 @@ const ActionsComponent: React.FC = ({ isModalVisible={isDisplayConfirmDeleteModal} isPlural={false} onCancel={handleToggleModal} - onConfirm={handleOnDeleteConfirm.bind(null, [{ id: caseData.id, title: caseData.title }])} + onConfirm={handleOnDeleteConfirm.bind(null, [ + { id: caseData.id, title: caseData.title, type: caseData.type }, + ])} /> ), // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 83a0c4e7acd3d..9bbf2db3d83c5 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -397,27 +397,29 @@ export const CaseComponent = React.memo( userCanCrud={userCanCrud} /> {(caseData.type !== CaseType.collection || hasDataToPush) && ( - + <> - {caseData.type !== CaseType.collection && ( - - - - )} - {hasDataToPush && ( - - {pushButton} - - )} - + + {caseData.type !== CaseType.collection && ( + + + + )} + {hasDataToPush && ( + + {pushButton} + + )} + + )} )} diff --git a/x-pack/plugins/security_solution/public/cases/containers/mock.ts b/x-pack/plugins/security_solution/public/cases/containers/mock.ts index d8692da986cbe..719fe01579285 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/mock.ts @@ -103,6 +103,37 @@ export const basicCase: Case = { subCaseIds: [], }; +export const collectionCase: Case = { + type: CaseType.collection, + closedAt: null, + closedBy: null, + id: 'collection-id', + comments: [basicComment], + createdAt: basicCreatedAt, + createdBy: elasticUser, + connector: { + id: '123', + name: 'My Connector', + type: ConnectorTypes.none, + fields: null, + }, + description: 'Security banana Issue', + externalService: null, + status: CaseStatuses.open, + tags, + title: 'Another horrible breach in a collection!!', + totalComment: 1, + totalAlerts: 0, + updatedAt: basicUpdatedAt, + updatedBy: elasticUser, + version: 'WzQ3LDFd', + settings: { + syncAlerts: true, + }, + subCases: [], + subCaseIds: [], +}; + export const basicCasePost: Case = { ...basicCase, updatedAt: null, diff --git a/x-pack/plugins/security_solution/public/cases/containers/types.ts b/x-pack/plugins/security_solution/public/cases/containers/types.ts index 09c911d93ea47..98e0ced2b6067 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/types.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/types.ts @@ -150,8 +150,8 @@ export interface ActionLicense { export interface DeleteCase { id: string; + type: CaseType | null; title?: string; - type?: CaseType; } export interface FieldMappings { diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx index 1525f145f9030..422eb0c92cbd8 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx @@ -6,6 +6,8 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; + +import { CaseType } from '../../../../case/common/api'; import { useDeleteCases, UseDeleteCase } from './use_delete_cases'; import * as api from './api'; @@ -13,7 +15,11 @@ jest.mock('./api'); describe('useDeleteCases', () => { const abortCtrl = new AbortController(); - const deleteObj = [{ id: '1' }, { id: '2' }, { id: '3' }]; + const deleteObj = [ + { id: '1', type: CaseType.individual }, + { id: '2', type: CaseType.individual }, + { id: '3', type: CaseType.individual }, + ]; const deleteArr = ['1', '2', '3']; it('init', async () => { await act(async () => {