Skip to content

Commit

Permalink
[Security Solution][Case] Add the ability to mark cases in progress i…
Browse files Browse the repository at this point in the history
…n cases table. (#89341)
  • Loading branch information
cnasikas authored Feb 18, 2021
1 parent 28fa873 commit 1e3b13a
Show file tree
Hide file tree
Showing 14 changed files with 279 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
import { case1 } from '../../objects/case';

import {
ALL_CASES_CLOSE_ACTION,
ALL_CASES_CLOSED_CASES_STATS,
ALL_CASES_COMMENTS_COUNT,
ALL_CASES_DELETE_ACTION,
ALL_CASES_IN_PROGRESS_CASES_STATS,
ALL_CASES_ITEM_ACTIONS_BTN,
ALL_CASES_NAME,
ALL_CASES_OPEN_CASES_COUNT,
ALL_CASES_OPEN_CASES_STATS,
Expand Down Expand Up @@ -91,8 +90,7 @@ describe('Cases', () => {
cy.get(ALL_CASES_COMMENTS_COUNT).should('have.text', '0');
cy.get(ALL_CASES_OPENED_ON).should('include.text', 'ago');
cy.get(ALL_CASES_SERVICE_NOW_INCIDENT).should('have.text', 'Not pushed');
cy.get(ALL_CASES_DELETE_ACTION).should('exist');
cy.get(ALL_CASES_CLOSE_ACTION).should('exist');
cy.get(ALL_CASES_ITEM_ACTIONS_BTN).should('exist');

goToCaseDetails();

Expand Down
6 changes: 2 additions & 4 deletions x-pack/plugins/security_solution/cypress/screens/all_cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ export const ALL_CASES_CASE = (id: string) => {
return `[data-test-subj="cases-table-row-${id}"]`;
};

export const ALL_CASES_CLOSE_ACTION = '[data-test-subj="action-close"]';

export const ALL_CASES_CLOSED_CASES_STATS = '[data-test-subj="closedStatsHeader"]';

export const ALL_CASES_COMMENTS_COUNT = '[data-test-subj="case-table-column-commentCount"]';
Expand All @@ -19,10 +17,10 @@ export const ALL_CASES_CREATE_NEW_CASE_BTN = '[data-test-subj="createNewCaseBtn"

export const ALL_CASES_CREATE_NEW_CASE_TABLE_BTN = '[data-test-subj="cases-table-add-case"]';

export const ALL_CASES_DELETE_ACTION = '[data-test-subj="action-delete"]';

export const ALL_CASES_IN_PROGRESS_CASES_STATS = '[data-test-subj="inProgressStatsHeader"]';

export const ALL_CASES_ITEM_ACTIONS_BTN = '[data-test-subj="euiCollapsedItemActionsButton"]';

export const ALL_CASES_NAME = '[data-test-subj="case-details-link"]';

export const ALL_CASES_OPEN_CASES_COUNT = '[data-test-subj="case-status-filter"]';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_t
import { CaseStatuses } from '../../../../../case/common/api';
import { Case, SubCase } from '../../containers/types';
import { UpdateCase } from '../../containers/use_get_cases';
import { statuses } from '../status';
import * as i18n from './translations';

interface GetActions {
Expand All @@ -26,43 +27,67 @@ export const getActions = ({
caseStatus,
dispatchUpdate,
deleteCaseOnClick,
}: GetActions): Array<DefaultItemIconButtonAction<Case>> => [
{
description: i18n.DELETE_CASE,
icon: 'trash',
name: i18n.DELETE_CASE,
onClick: deleteCaseOnClick,
type: 'icon',
'data-test-subj': 'action-delete',
},
{
available: (item) => caseStatus === CaseStatuses.open && !hasSubCases(item.subCases),
description: i18n.CLOSE_CASE,
icon: 'folderCheck',
name: i18n.CLOSE_CASE,
}: GetActions): Array<DefaultItemIconButtonAction<Case>> => {
const openCaseAction = {
available: (item: Case) => caseStatus !== CaseStatuses.open && !hasSubCases(item.subCases),
description: statuses[CaseStatuses.open].actions.single.title,
icon: statuses[CaseStatuses.open].icon,
name: statuses[CaseStatuses.open].actions.single.title,
onClick: (theCase: Case) =>
dispatchUpdate({
updateKey: 'status',
updateValue: CaseStatuses.closed,
updateValue: CaseStatuses.open,
caseId: theCase.id,
version: theCase.version,
}),
type: 'icon',
'data-test-subj': 'action-close',
},
{
available: (item) => caseStatus !== CaseStatuses.open && !hasSubCases(item.subCases),
description: i18n.REOPEN_CASE,
icon: 'folderExclamation',
name: i18n.REOPEN_CASE,
type: 'icon' as const,
'data-test-subj': 'action-open',
};

const makeInProgressAction = {
available: (item: Case) =>
caseStatus !== CaseStatuses['in-progress'] && !hasSubCases(item.subCases),
description: statuses[CaseStatuses['in-progress']].actions.single.title,
icon: statuses[CaseStatuses['in-progress']].icon,
name: statuses[CaseStatuses['in-progress']].actions.single.title,
onClick: (theCase: Case) =>
dispatchUpdate({
updateKey: 'status',
updateValue: CaseStatuses.open,
updateValue: CaseStatuses['in-progress'],
caseId: theCase.id,
version: theCase.version,
}),
type: 'icon',
'data-test-subj': 'action-open',
},
];
type: 'icon' as const,
'data-test-subj': 'action-in-progress',
};

const closeCaseAction = {
available: (item: Case) => caseStatus !== CaseStatuses.closed && !hasSubCases(item.subCases),
description: statuses[CaseStatuses.closed].actions.single.title,
icon: statuses[CaseStatuses.closed].icon,
name: statuses[CaseStatuses.closed].actions.single.title,
onClick: (theCase: Case) =>
dispatchUpdate({
updateKey: 'status',
updateValue: CaseStatuses.closed,
caseId: theCase.id,
version: theCase.version,
}),
type: 'icon' as const,
'data-test-subj': 'action-close',
};

return [
{
description: i18n.DELETE_CASE,
icon: 'trash',
name: i18n.DELETE_CASE,
onClick: deleteCaseOnClick,
type: 'icon',
'data-test-subj': 'action-delete',
},
openCaseAction,
makeInProgressAction,
closeCaseAction,
];
};
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ describe('AllCases', () => {
</TestProviders>
);
await waitFor(() => {
wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click');
wrapper.find('[data-test-subj="action-close"]').first().simulate('click');
const firstCase = useGetCasesMockState.data.cases[0];
expect(dispatchUpdateCaseProperty).toBeCalledWith({
Expand All @@ -305,6 +306,7 @@ describe('AllCases', () => {
</TestProviders>
);
await waitFor(() => {
wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click');
wrapper.find('[data-test-subj="action-open"]').first().simulate('click');
const firstCase = useGetCasesMockState.data.cases[0];
expect(dispatchUpdateCaseProperty).toBeCalledWith({
Expand All @@ -317,6 +319,26 @@ describe('AllCases', () => {
});
});

it('put case in progress when row action icon clicked', async () => {
const wrapper = mount(
<TestProviders>
<AllCases userCanCrud={true} />
</TestProviders>
);
await waitFor(() => {
wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click');
wrapper.find('[data-test-subj="action-in-progress"]').first().simulate('click');
const firstCase = useGetCasesMockState.data.cases[0];
expect(dispatchUpdateCaseProperty).toBeCalledWith({
caseId: firstCase.id,
updateKey: 'status',
updateValue: CaseStatuses['in-progress'],
refetchCasesStatus: fetchCasesStatus,
version: firstCase.version,
});
});
});

it('Bulk delete', async () => {
useGetCasesMock.mockReturnValue({
...defaultGetCases,
Expand Down Expand Up @@ -395,6 +417,27 @@ describe('AllCases', () => {
});
});

it('Bulk in-progress status update', async () => {
useGetCasesMock.mockReturnValue({
...defaultGetCases,
selectedCases: useGetCasesMockState.data.cases,
});

const wrapper = mount(
<TestProviders>
<AllCases userCanCrud={true} />
</TestProviders>
);
await waitFor(() => {
wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click');
wrapper.find('[data-test-subj="cases-bulk-in-progress-button"]').first().simulate('click');
expect(updateBulkStatus).toBeCalledWith(
useGetCasesMockState.data.cases,
CaseStatuses['in-progress']
);
});
});

it('isDeleted is true, refetch', async () => {
useDeleteCasesMock.mockReturnValue({
...defaultDeleteCases,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import React from 'react';
import { EuiContextMenuItem } from '@elastic/eui';

import { CaseStatuses } from '../../../../../case/common/api';
import { statuses } from '../status';
import * as i18n from './translations';

interface GetBulkItems {
caseStatus: string;
caseStatus: CaseStatuses;
closePopover: () => void;
deleteCasesAction: (cases: string[]) => void;
selectedCaseIds: string[];
Expand All @@ -26,34 +27,72 @@ export const getBulkItems = ({
selectedCaseIds,
updateCaseStatus,
}: GetBulkItems) => {
let statusMenuItems: JSX.Element[] = [];

const openMenuItem = (
<EuiContextMenuItem
data-test-subj="cases-bulk-open-button"
disabled={selectedCaseIds.length === 0}
key="cases-bulk-open-button"
icon={statuses[CaseStatuses.open].icon}
onClick={() => {
closePopover();
updateCaseStatus(CaseStatuses.open);
}}
>
{statuses[CaseStatuses.open].actions.bulk.title}
</EuiContextMenuItem>
);

const inProgressMenuItem = (
<EuiContextMenuItem
data-test-subj="cases-bulk-in-progress-button"
disabled={selectedCaseIds.length === 0}
key="cases-bulk-in-progress-button"
icon={statuses[CaseStatuses['in-progress']].icon}
onClick={() => {
closePopover();
updateCaseStatus(CaseStatuses['in-progress']);
}}
>
{statuses[CaseStatuses['in-progress']].actions.bulk.title}
</EuiContextMenuItem>
);

const closeMenuItem = (
<EuiContextMenuItem
data-test-subj="cases-bulk-close-button"
disabled={selectedCaseIds.length === 0}
key="cases-bulk-close-button"
icon={statuses[CaseStatuses.closed].icon}
onClick={() => {
closePopover();
updateCaseStatus(CaseStatuses.closed);
}}
>
{statuses[CaseStatuses.closed].actions.bulk.title}
</EuiContextMenuItem>
);

switch (caseStatus) {
case CaseStatuses.open:
statusMenuItems = [inProgressMenuItem, closeMenuItem];
break;

case CaseStatuses['in-progress']:
statusMenuItems = [openMenuItem, closeMenuItem];
break;

case CaseStatuses.closed:
statusMenuItems = [openMenuItem, inProgressMenuItem];
break;

default:
break;
}

return [
caseStatus === CaseStatuses.open ? (
<EuiContextMenuItem
data-test-subj="cases-bulk-close-button"
disabled={selectedCaseIds.length === 0}
key={i18n.BULK_ACTION_CLOSE_SELECTED}
icon="folderCheck"
onClick={() => {
closePopover();
updateCaseStatus(CaseStatuses.closed);
}}
>
{i18n.BULK_ACTION_CLOSE_SELECTED}
</EuiContextMenuItem>
) : (
<EuiContextMenuItem
data-test-subj="cases-bulk-open-button"
disabled={selectedCaseIds.length === 0}
key={i18n.BULK_ACTION_OPEN_SELECTED}
icon="folderExclamation"
onClick={() => {
closePopover();
updateCaseStatus(CaseStatuses.open);
}}
>
{i18n.BULK_ACTION_OPEN_SELECTED}
</EuiContextMenuItem>
),
...statusMenuItems,
<EuiContextMenuItem
data-test-subj="cases-bulk-delete-button"
key={i18n.BULK_ACTION_DELETE_SELECTED}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,6 @@

import { i18n } from '@kbn/i18n';

export const BULK_ACTION_CLOSE_SELECTED = i18n.translate(
'xpack.securitySolution.case.caseTable.bulkActions.closeSelectedTitle',
{
defaultMessage: 'Close selected',
}
);

export const BULK_ACTION_OPEN_SELECTED = i18n.translate(
'xpack.securitySolution.case.caseTable.bulkActions.openSelectedTitle',
{
defaultMessage: 'Reopen selected',
}
);

export const BULK_ACTION_DELETE_SELECTED = i18n.translate(
'xpack.securitySolution.case.caseTable.bulkActions.deleteSelectedTitle',
{
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ describe('StatusActionButton', () => {

expect(
wrapper.find(`[data-test-subj="case-view-status-action-button"]`).first().prop('iconType')
).toBe('folderCheck');
).toBe('folderClosed');
});

it('it renders the correct button icon: status closed', () => {
const wrapper = mount(<StatusActionButton {...defaultProps} status={CaseStatuses.closed} />);

expect(
wrapper.find(`[data-test-subj="case-view-status-action-button"]`).first().prop('iconType')
).toBe('folderCheck');
).toBe('folderOpen');
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const StatusActionButtonComponent: React.FC<Props> = ({
return (
<EuiButton
data-test-subj="case-view-status-action-button"
iconType={statuses[caseStatuses[nextStatusIndex]].button.icon}
iconType={statuses[caseStatuses[nextStatusIndex]].icon}
isDisabled={disabled}
isLoading={isLoading}
onClick={onClick}
Expand Down
Loading

0 comments on commit 1e3b13a

Please sign in to comment.