Skip to content

Commit

Permalink
[SIEM] [Case] Design fixing (#61681) (#61862)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephmilovic committed Mar 30, 2020
1 parent fab0b8f commit bc93f27
Show file tree
Hide file tree
Showing 15 changed files with 161 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiButtonIcon, EuiPopover, EuiSelectableOption } from '@elastic/eui';
import { EuiButtonIcon, EuiPopover, EuiSelectableOption, EuiToolTip } from '@elastic/eui';
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { useDispatch } from 'react-redux';
Expand Down Expand Up @@ -62,13 +62,15 @@ export const InsertTimelinePopoverComponent: React.FC<Props> = ({

const insertTimelineButton = useMemo(
() => (
<EuiButtonIcon
aria-label={i18n.INSERT_TIMELINE}
data-test-subj="insert-timeline-button"
iconType="timeline"
isDisabled={isDisabled}
onClick={handleOpenPopover}
/>
<EuiToolTip position="top" content={<p>{i18n.INSERT_TIMELINE}</p>}>
<EuiButtonIcon
aria-label={i18n.INSERT_TIMELINE}
data-test-subj="insert-timeline-button"
iconType="timeline"
isDisabled={isDisabled}
onClick={handleOpenPopover}
/>
</EuiToolTip>
),
[handleOpenPopover, isDisabled]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ export const SEARCH_BOX_TIMELINE_PLACEHOLDER = i18n.translate(
);

export const INSERT_TIMELINE = i18n.translate('xpack.siem.insert.timeline.insertTimelineButton', {
defaultMessage: 'Insert Timeline…',
defaultMessage: 'Insert timeline link',
});
40 changes: 40 additions & 0 deletions x-pack/legacy/plugins/siem/public/containers/case/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,46 @@ export const ERROR_TITLE = i18n.translate('xpack.siem.containers.case.errorTitle
defaultMessage: 'Error fetching data',
});

export const ERROR_DELETING = i18n.translate('xpack.siem.containers.case.errorDeletingTitle', {
defaultMessage: 'Error deleting data',
});

export const UPDATED_CASE = (caseTitle: string) =>
i18n.translate('xpack.siem.containers.case.updatedCase', {
values: { caseTitle },
defaultMessage: 'Updated "{caseTitle}"',
});

export const DELETED_CASES = (totalCases: number, caseTitle?: string) =>
i18n.translate('xpack.siem.containers.case.deletedCases', {
values: { caseTitle, totalCases },
defaultMessage: 'Deleted {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}',
});

export const CLOSED_CASES = ({
totalCases,
caseTitle,
}: {
totalCases: number;
caseTitle?: string;
}) =>
i18n.translate('xpack.siem.containers.case.closedCases', {
values: { caseTitle, totalCases },
defaultMessage: 'Closed {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}',
});

export const REOPENED_CASES = ({
totalCases,
caseTitle,
}: {
totalCases: number;
caseTitle?: string;
}) =>
i18n.translate('xpack.siem.containers.case.reopenedCases', {
values: { caseTitle, totalCases },
defaultMessage: 'Reopened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}',
});

export const TAG_FETCH_FAILURE = i18n.translate(
'xpack.siem.containers.case.tagFetchFailDescription',
{
Expand Down
5 changes: 5 additions & 0 deletions x-pack/legacy/plugins/siem/public/containers/case/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,8 @@ export interface ActionLicense {
enabledInConfig: boolean;
enabledInLicense: boolean;
}

export interface DeleteCase {
id: string;
title?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { useCallback, useReducer } from 'react';
import { errorToToaster, useStateToaster } from '../../components/toasters';
import { displaySuccessToast, errorToToaster, useStateToaster } from '../../components/toasters';
import * as i18n from './translations';
import { patchCasesStatus } from './api';
import { BulkUpdateStatus, Case } from './types';
Expand Down Expand Up @@ -71,9 +71,22 @@ export const useUpdateCases = (): UseUpdateCase => {
const patchData = async () => {
try {
dispatch({ type: 'FETCH_INIT' });
await patchCasesStatus(cases, abortCtrl.signal);
const patchResponse = await patchCasesStatus(cases, abortCtrl.signal);
if (!cancel) {
const resultCount = Object.keys(patchResponse).length;
const firstTitle = patchResponse[0].title;

dispatch({ type: 'FETCH_SUCCESS', payload: true });
const messageArgs = {
totalCases: resultCount,
caseTitle: resultCount === 1 ? firstTitle : '',
};
const message =
resultCount && patchResponse[0].status === 'open'
? i18n.REOPENED_CASES(messageArgs)
: i18n.CLOSED_CASES(messageArgs);

displaySuccessToast(message, dispatchToaster);
}
} catch (error) {
if (!cancel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
*/

import { useCallback, useReducer } from 'react';
import { errorToToaster, useStateToaster } from '../../components/toasters';
import { displaySuccessToast, errorToToaster, useStateToaster } from '../../components/toasters';
import * as i18n from './translations';
import { deleteCases } from './api';
import { DeleteCase } from './types';

interface DeleteState {
isDisplayConfirmDeleteModal: boolean;
Expand Down Expand Up @@ -57,9 +58,10 @@ const dataFetchReducer = (state: DeleteState, action: Action): DeleteState => {
return state;
}
};

interface UseDeleteCase extends DeleteState {
dispatchResetIsDeleted: () => void;
handleOnDeleteConfirm: (caseIds: string[]) => void;
handleOnDeleteConfirm: (caseIds: DeleteCase[]) => void;
handleToggleModal: () => void;
}

Expand All @@ -72,21 +74,26 @@ export const useDeleteCases = (): UseDeleteCase => {
});
const [, dispatchToaster] = useStateToaster();

const dispatchDeleteCases = useCallback((caseIds: string[]) => {
const dispatchDeleteCases = useCallback((cases: DeleteCase[]) => {
let cancel = false;
const abortCtrl = new AbortController();

const deleteData = async () => {
try {
dispatch({ type: 'FETCH_INIT' });
const caseIds = cases.map(theCase => theCase.id);
await deleteCases(caseIds, abortCtrl.signal);
if (!cancel) {
dispatch({ type: 'FETCH_SUCCESS', payload: true });
displaySuccessToast(
i18n.DELETED_CASES(cases.length, cases.length === 1 ? cases[0].title : ''),
dispatchToaster
);
}
} catch (error) {
if (!cancel) {
errorToToaster({
title: i18n.ERROR_TITLE,
title: i18n.ERROR_DELETING,
error: error.body && error.body.message ? new Error(error.body.message) : error,
dispatchToaster,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
*/

import { useReducer, useCallback } from 'react';
import { displaySuccessToast, errorToToaster, useStateToaster } from '../../components/toasters';
import { CasePatchRequest } from '../../../../../../plugins/case/common/api';
import { errorToToaster, useStateToaster } from '../../components/toasters';

import { patchCase } from './api';
import * as i18n from './translations';
Expand Down Expand Up @@ -94,6 +94,7 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase =>
updateCase(response[0]);
}
dispatch({ type: 'FETCH_SUCCESS' });
displaySuccessToast(i18n.UPDATED_CASE(response[0].title), dispatchToaster);
}
} catch (error) {
if (!cancel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ describe('AllCases', () => {
.last()
.simulate('click');
expect(handleOnDeleteConfirm.mock.calls[0][0]).toStrictEqual(
useGetCasesMockState.data.cases.map(theCase => theCase.id)
useGetCasesMockState.data.cases.map(({ id }) => ({ id }))
);
});
it('Bulk close status update', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import styled, { css } from 'styled-components';
import * as i18n from './translations';

import { getCasesColumns } from './columns';
import { Case, FilterOptions, SortFieldCase } from '../../../../containers/case/types';
import { Case, DeleteCase, FilterOptions, SortFieldCase } from '../../../../containers/case/types';
import { useGetCases, UpdateCase } from '../../../../containers/case/use_get_cases';
import { useGetCasesStatus } from '../../../../containers/case/use_get_cases_status';
import { useDeleteCases } from '../../../../containers/case/use_delete_cases';
Expand Down Expand Up @@ -107,11 +107,24 @@ export const AllCases = React.memo(() => {
isDisplayConfirmDeleteModal,
} = useDeleteCases();

const { dispatchResetIsUpdated, isUpdated, updateBulkStatus } = useUpdateCases();
// Update case
const {
dispatchResetIsUpdated,
isLoading: isUpdating,
isUpdated,
updateBulkStatus,
} = useUpdateCases();
const [deleteThisCase, setDeleteThisCase] = useState({
title: '',
id: '',
});
const [deleteBulk, setDeleteBulk] = useState<DeleteCase[]>([]);

const refreshCases = useCallback(() => {
refetchCases(filterOptions, queryParams);
fetchCasesStatus();
setSelectedCases([]);
setDeleteBulk([]);
}, [filterOptions, queryParams]);

useEffect(() => {
Expand All @@ -124,11 +137,6 @@ export const AllCases = React.memo(() => {
dispatchResetIsUpdated();
}
}, [isDeleted, isUpdated]);
const [deleteThisCase, setDeleteThisCase] = useState({
title: '',
id: '',
});
const [deleteBulk, setDeleteBulk] = useState<string[]>([]);
const confirmDeleteModal = useMemo(
() => (
<ConfirmDeleteCaseModal
Expand All @@ -138,7 +146,7 @@ export const AllCases = React.memo(() => {
onCancel={handleToggleModal}
onConfirm={handleOnDeleteConfirm.bind(
null,
deleteBulk.length > 0 ? deleteBulk : [deleteThisCase.id]
deleteBulk.length > 0 ? deleteBulk : [deleteThisCase]
)}
/>
),
Expand All @@ -150,10 +158,20 @@ export const AllCases = React.memo(() => {
setDeleteThisCase(deleteCase);
}, []);

const toggleBulkDeleteModal = useCallback((deleteCases: string[]) => {
handleToggleModal();
setDeleteBulk(deleteCases);
}, []);
const toggleBulkDeleteModal = useCallback(
(caseIds: string[]) => {
handleToggleModal();
if (caseIds.length === 1) {
const singleCase = selectedCases.find(theCase => theCase.id === caseIds[0]);
if (singleCase) {
return setDeleteThisCase({ id: singleCase.id, title: singleCase.title });
}
}
const convertToDeleteCases: DeleteCase[] = caseIds.map(id => ({ id }));
setDeleteBulk(convertToDeleteCases);
},
[selectedCases]
);

const handleUpdateCaseStatus = useCallback(
(status: string) => {
Expand Down Expand Up @@ -289,7 +307,7 @@ export const AllCases = React.memo(() => {
</EuiFlexItem>
</EuiFlexGroup>
</CaseHeaderPage>
{(isCasesLoading || isDeleting) && !isDataEmpty && (
{(isCasesLoading || isDeleting || isUpdating) && !isDataEmpty && (
<ProgressLoader size="xs" color="accent" className="essentialAnimation" />
)}
<Panel loading={isCasesLoading}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ 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([data.id]);
expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([{ id: data.id, title: data.title }]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const CaseViewActionsComponent: React.FC<CaseViewActions> = ({ caseData }) => {
isModalVisible={isDisplayConfirmDeleteModal}
isPlural={false}
onCancel={handleToggleModal}
onConfirm={handleOnDeleteConfirm.bind(null, [caseData.id])}
onConfirm={handleOnDeleteConfirm.bind(null, [{ id: caseData.id, title: caseData.title }])}
/>
),
[isDisplayConfirmDeleteModal, caseData]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ export const REQUIRED_UPDATE_TO_SERVICE = i18n.translate(
}
);

export const COPY_LINK_COMMENT = i18n.translate('xpack.siem.case.caseView.copyCommentLinkAria', {
defaultMessage: 'click to copy comment link',
export const COPY_REFERENCE_LINK = i18n.translate('xpack.siem.case.caseView.copyCommentLinkAria', {
defaultMessage: 'Copy reference link',
});

export const MOVE_TO_ORIGINAL_COMMENT = i18n.translate(
'xpack.siem.case.caseView.moveToCommentAria',
{
defaultMessage: 'click to highlight the reference comment',
defaultMessage: 'Highlight the referenced comment',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export const UserActionItem = ({
labelQuoteAction={labelQuoteAction}
labelTitle={labelTitle ?? <></>}
linkId={linkId}
fullName={fullName}
username={username}
updatedAt={updatedAt}
onEdit={onEdit}
Expand Down
Loading

0 comments on commit bc93f27

Please sign in to comment.