Skip to content

Commit

Permalink
[Security Solution][Case] Attach/Sync alert to case: Fix toast messag…
Browse files Browse the repository at this point in the history
…es (#87206)
  • Loading branch information
cnasikas authored Jan 6, 2021
1 parent 34a3982 commit 8ead390
Show file tree
Hide file tree
Showing 14 changed files with 353 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateKey: 'title',
updateValue: titleUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
caseData,
onSuccess,
onError,
});
Expand All @@ -194,7 +194,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateKey: 'connector',
updateValue: connector,
updateCase: handleUpdateNewCase,
version: caseData.version,
caseData,
onSuccess,
onError,
});
Expand All @@ -208,7 +208,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateKey: 'description',
updateValue: descriptionUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
caseData,
onSuccess,
onError,
});
Expand All @@ -221,7 +221,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateKey: 'tags',
updateValue: tagsUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
caseData,
onSuccess,
onError,
});
Expand All @@ -234,7 +234,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateKey: 'status',
updateValue: statusUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
caseData,
onSuccess,
onError,
});
Expand All @@ -248,7 +248,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateKey: 'settings',
updateValue: settingsUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
caseData,
onSuccess,
onError,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import {
import { CommentType } from '../../../../../case/common/api';
import { Ecs } from '../../../../common/ecs';
import { ActionIconItem } from '../../../timelines/components/timeline/body/actions/action_icon_item';
import * as i18n from './translations';
import { usePostComment } from '../../containers/use_post_comment';
import { Case } from '../../containers/types';
import { displaySuccessToast, useStateToaster } from '../../../common/components/toasters';
import { useStateToaster } from '../../../common/components/toasters';
import { useCreateCaseModal } from '../use_create_case_modal';
import { useAllCasesModal } from '../use_all_cases_modal';
import { createUpdateSuccessToaster } from './helpers';
import * as i18n from './translations';

interface AddToCaseActionProps {
ariaLabel?: string;
Expand Down Expand Up @@ -53,7 +54,7 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
alertId: eventId,
index: eventIndex ?? '',
},
() => displaySuccessToast(i18n.CASE_CREATED_SUCCESS_TOAST(theCase.title), dispatchToaster)
() => dispatchToaster({ type: 'addToaster', toast: createUpdateSuccessToaster(theCase) })
);
},
[postComment, eventId, eventIndex, dispatchToaster]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { createUpdateSuccessToaster } from './helpers';
import { Case } from '../../containers/types';

const theCase = {
title: 'My case',
settings: {
syncAlerts: true,
},
} as Case;

describe('helpers', () => {
describe('createUpdateSuccessToaster', () => {
it('creates the correct toast when the sync alerts is on', () => {
// We remove the id as is randomly generated
const { id, ...toast } = createUpdateSuccessToaster(theCase);
expect(toast).toEqual({
color: 'success',
iconType: 'check',
text: 'Alerts in this case have their status synched with the case status',
title: 'An alert has been added to "My case"',
});
});

it('creates the correct toast when the sync alerts is off', () => {
// We remove the id as is randomly generated
const { id, ...toast } = createUpdateSuccessToaster({
...theCase,
settings: { syncAlerts: false },
});
expect(toast).toEqual({
color: 'success',
iconType: 'check',
title: 'An alert has been added to "My case"',
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import uuid from 'uuid';
import { AppToast } from '../../../common/components/toasters';
import { Case } from '../../containers/types';
import * as i18n from './translations';

export const createUpdateSuccessToaster = (theCase: Case): AppToast => {
const toast: AppToast = {
id: uuid.v4(),
color: 'success',
iconType: 'check',
title: i18n.CASE_CREATED_SUCCESS_TOAST(theCase.title),
};

if (theCase.settings.syncAlerts) {
return { ...toast, text: i18n.CASE_CREATED_SUCCESS_TOAST_TEXT };
}

return toast;
};
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,10 @@ export const CASE_CREATED_SUCCESS_TOAST = (title: string) =>
values: { title },
defaultMessage: 'An alert has been added to "{title}"',
});

export const CASE_CREATED_SUCCESS_TOAST_TEXT = i18n.translate(
'xpack.securitySolution.case.timeline.actions.caseCreatedSuccessToastText',
{
defaultMessage: 'Alerts in this case have their status synched with the case status',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,10 @@ export const postCase = async (newCase: CasePostRequest, signal: AbortSignal): P

export const patchCase = async (
caseId: string,
updatedCase: Pick<CasePatchRequest, 'description' | 'status' | 'tags' | 'title'>,
updatedCase: Pick<
CasePatchRequest,
'description' | 'status' | 'tags' | 'title' | 'settings' | 'connector'
>,
version: string,
signal: AbortSignal
): Promise<Case[]> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,16 @@ export const ERROR_GET_FIELDS = i18n.translate(
defaultMessage: 'Error getting fields from service',
}
);

export const SYNC_CASE = (caseTitle: string) =>
i18n.translate('xpack.securitySolution.containers.case.syncCase', {
values: { caseTitle },
defaultMessage: 'Alerts in "{caseTitle}" have been synced',
});

export const STATUS_CHANGED_TOASTER_TEXT = i18n.translate(
'xpack.securitySolution.case.containers.statusChangeToasterText',
{
defaultMessage: 'Alerts in this case have been also had their status updated',
}
);
16 changes: 16 additions & 0 deletions x-pack/plugins/security_solution/public/cases/containers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
CommentRequest,
CaseStatuses,
CaseAttributes,
CasePatchRequest,
} from '../../../../case/common/api';

export { CaseConnector, ActionConnector } from '../../../../case/common/api';
Expand Down Expand Up @@ -137,3 +138,18 @@ export interface FieldMappings {
id: string;
title?: string;
}

export type UpdateKey = keyof Pick<
CasePatchRequest,
'connector' | 'description' | 'status' | 'tags' | 'title' | 'settings'
>;

export interface UpdateByKey {
updateKey: UpdateKey;
updateValue: CasePatchRequest[UpdateKey];
fetchCaseUserActions?: (caseId: string) => void;
updateCase?: (newCase: Case) => void;
caseData: Case;
onSuccess?: () => void;
onError?: () => void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
useGetCases,
UseGetCases,
} from './use_get_cases';
import { UpdateKey } from './use_update_case';
import { UpdateKey } from './types';
import { allCases, basicCase } from './mock';
import * as api from './api';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
import { useCallback, useEffect, useReducer } from 'react';
import { CaseStatuses } from '../../../../case/common/api';
import { DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from './constants';
import { AllCases, SortFieldCase, FilterOptions, QueryParams, Case } from './types';
import { AllCases, SortFieldCase, FilterOptions, QueryParams, Case, UpdateByKey } from './types';
import { errorToToaster, useStateToaster } from '../../common/components/toasters';
import * as i18n from './translations';
import { UpdateByKey } from './use_update_case';
import { getCases, patchCase } from './api';

export interface UseGetCasesState {
Expand All @@ -22,7 +21,7 @@ export interface UseGetCasesState {
selectedCases: Case[];
}

export interface UpdateCase extends UpdateByKey {
export interface UpdateCase extends Omit<UpdateByKey, 'caseData'> {
caseId: string;
version: string;
refetchCasesStatus: () => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
*/

import { renderHook, act } from '@testing-library/react-hooks';
import { useUpdateCase, UseUpdateCase, UpdateKey } from './use_update_case';
import { useUpdateCase, UseUpdateCase } from './use_update_case';
import { basicCase } from './mock';
import * as api from './api';
import { UpdateKey } from './types';

jest.mock('./api');

Expand All @@ -24,7 +25,7 @@ describe('useUpdateCase', () => {
updateKey,
updateValue: 'updated description',
updateCase,
version: basicCase.version,
caseData: basicCase,
onSuccess,
onError,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,19 @@

import { useReducer, useCallback } from 'react';

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

import { patchCase } from './api';
import { UpdateKey, UpdateByKey } from './types';
import * as i18n from './translations';
import { Case } from './types';

export type UpdateKey = keyof Pick<
CasePatchRequest,
'connector' | 'description' | 'status' | 'tags' | 'title' | 'settings'
>;
import { createUpdateSuccessToaster } from './utils';

interface NewCaseState {
isLoading: boolean;
isError: boolean;
updateKey: UpdateKey | null;
}

export interface UpdateByKey {
updateKey: UpdateKey;
updateValue: CasePatchRequest[UpdateKey];
fetchCaseUserActions?: (caseId: string) => void;
updateCase?: (newCase: Case) => void;
version: string;
onSuccess?: () => void;
onError?: () => void;
}

type Action =
| { type: 'FETCH_INIT'; payload: UpdateKey }
| { type: 'FETCH_SUCCESS' }
Expand Down Expand Up @@ -89,7 +70,7 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase =>
updateKey,
updateValue,
updateCase,
version,
caseData,
onSuccess,
onError,
}: UpdateByKey) => {
Expand All @@ -101,7 +82,7 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase =>
const response = await patchCase(
caseId,
{ [updateKey]: updateValue },
version,
caseData.version,
abortCtrl.signal
);
if (!cancel) {
Expand All @@ -112,7 +93,11 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase =>
updateCase(response[0]);
}
dispatch({ type: 'FETCH_SUCCESS' });
displaySuccessToast(i18n.UPDATED_CASE(response[0].title), dispatchToaster);
dispatchToaster({
type: 'addToaster',
toast: createUpdateSuccessToaster(caseData, response[0], updateKey, updateValue),
});

if (onSuccess) {
onSuccess();
}
Expand Down
Loading

0 comments on commit 8ead390

Please sign in to comment.