Skip to content

Commit

Permalink
[Security Solution][Case] Fix individual case deletion on case view (e…
Browse files Browse the repository at this point in the history
…lastic#93218) (elastic#93564)

# Conflicts:
#	x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx
  • Loading branch information
cnasikas authored Mar 4, 2021
1 parent bb1f1d9 commit 8c8993d
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
},
]);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export const AllCases = React.memo<AllCasesProps>(
const [deleteThisCase, setDeleteThisCase] = useState<DeleteCase>({
title: '',
id: '',
type: null,
});
const [deleteBulk, setDeleteBulk] = useState<DeleteCase[]>([]);
const filterRefetch = useRef<() => void>();
Expand Down Expand Up @@ -230,10 +231,10 @@ export const AllCases = React.memo<AllCasesProps>(
);

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,
Expand All @@ -242,10 +243,14 @@ export const AllCases = React.memo<AllCasesProps>(
});
}
}
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(
Expand All @@ -255,11 +260,6 @@ export const AllCases = React.memo<AllCasesProps>(
[selectedCases, updateBulkStatus]
);

const selectedCaseIds = useMemo(
(): string[] => selectedCases.map((caseObj: Case) => caseObj.id),
[selectedCases]
);

const getBulkItemsPopoverContent = useCallback(
(closePopover: () => void) => (
<EuiContextMenuPanel
Expand All @@ -268,19 +268,13 @@ export const AllCases = React.memo<AllCasesProps>(
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<UpdateCase, 'refetchCasesStatus'>) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -25,7 +26,7 @@ export const getBulkItems = ({
caseStatus,
closePopover,
deleteCasesAction,
selectedCaseIds,
selectedCases,
updateCaseStatus,
includeCollections,
}: GetBulkItems) => {
Expand All @@ -34,7 +35,7 @@ export const getBulkItems = ({
const openMenuItem = (
<EuiContextMenuItem
data-test-subj="cases-bulk-open-button"
disabled={selectedCaseIds.length === 0 || includeCollections}
disabled={selectedCases.length === 0 || includeCollections}
key={i18n.BULK_ACTION_OPEN_SELECTED}
icon="folderOpen"
onClick={() => {
Expand All @@ -49,7 +50,7 @@ export const getBulkItems = ({
const closeMenuItem = (
<EuiContextMenuItem
data-test-subj="cases-bulk-close-button"
disabled={selectedCaseIds.length === 0 || includeCollections}
disabled={selectedCases.length === 0 || includeCollections}
key={i18n.BULK_ACTION_CLOSE_SELECTED}
icon="folderCheck"
onClick={() => {
Expand Down Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
]);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ const ActionsComponent: React.FC<CaseViewActions> = ({
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,27 +397,29 @@ export const CaseComponent = React.memo<CaseProps>(
userCanCrud={userCanCrud}
/>
{(caseData.type !== CaseType.collection || hasDataToPush) && (
<EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexEnd">
<>
<MyEuiHorizontalRule
margin="s"
data-test-subj="case-view-bottom-actions-horizontal-rule"
/>
{caseData.type !== CaseType.collection && (
<EuiFlexItem grow={false}>
<StatusActionButton
status={caseData.status}
onStatusChanged={changeStatus}
disabled={!userCanCrud}
isLoading={isLoading && updateKey === 'status'}
/>
</EuiFlexItem>
)}
{hasDataToPush && (
<EuiFlexItem data-test-subj="has-data-to-push-button" grow={false}>
{pushButton}
</EuiFlexItem>
)}
</EuiFlexGroup>
<EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexEnd">
{caseData.type !== CaseType.collection && (
<EuiFlexItem grow={false}>
<StatusActionButton
status={caseData.status}
onStatusChanged={changeStatus}
disabled={!userCanCrud}
isLoading={isLoading && updateKey === 'status'}
/>
</EuiFlexItem>
)}
{hasDataToPush && (
<EuiFlexItem data-test-subj="has-data-to-push-button" grow={false}>
{pushButton}
</EuiFlexItem>
)}
</EuiFlexGroup>
</>
)}
</>
)}
Expand Down
31 changes: 31 additions & 0 deletions x-pack/plugins/security_solution/public/cases/containers/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ export interface ActionLicense {

export interface DeleteCase {
id: string;
type: CaseType | null;
title?: string;
type?: CaseType;
}

export interface FieldMappings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
*/

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';

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 () => {
Expand Down

0 comments on commit 8c8993d

Please sign in to comment.