Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution][Case] Attach/Sync alert to case: Fix toast messages #87206

Merged
merged 2 commits into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateKey: 'title',
updateValue: titleUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
caseData,
onSuccess,
onError,
});
Expand All @@ -189,7 +189,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateKey: 'connector',
updateValue: connector,
updateCase: handleUpdateNewCase,
version: caseData.version,
caseData,
onSuccess,
onError,
});
Expand All @@ -203,7 +203,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateKey: 'description',
updateValue: descriptionUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
caseData,
onSuccess,
onError,
});
Expand All @@ -216,7 +216,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateKey: 'tags',
updateValue: tagsUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
caseData,
onSuccess,
onError,
});
Expand All @@ -229,7 +229,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateKey: 'status',
updateValue: statusUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
caseData,
onSuccess,
onError,
});
Expand All @@ -243,7 +243,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 @@ -72,3 +72,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,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to know when updating the case if it has alerts. For that reason, we replaced the version with the whole Case object.

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