Skip to content

Commit

Permalink
Fix toasts
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas committed Jan 5, 2021
1 parent 1363a2a commit 12dba72
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 42 deletions.
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 @@ -5,6 +5,7 @@
*/

import React, { memo, useState, useCallback, useMemo } from 'react';
import uuid from 'uuid';
import {
EuiPopover,
EuiButtonIcon,
Expand All @@ -20,10 +21,25 @@ import { ActionIconItem } from '../../../timelines/components/timeline/body/acti
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 { AppToast, useStateToaster } from '../../../common/components/toasters';
import { useCreateCaseModal } from '../use_create_case_modal';
import { useAllCasesModal } from '../use_all_cases_modal';

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

interface AddToCaseActionProps {
ariaLabel?: string;
ecsRowData: Ecs;
Expand Down Expand Up @@ -53,7 +69,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
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,
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
52 changes: 50 additions & 2 deletions x-pack/plugins/security_solution/public/cases/containers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import uuid from 'uuid';
import { set } from '@elastic/safer-lodash-set';
import { camelCase, isArray, isObject } from 'lodash';
import { fold } from 'fp-ts/lib/Either';
Expand All @@ -26,9 +27,12 @@ import {
CaseUserActionsResponseRt,
ServiceConnectorCaseResponseRt,
ServiceConnectorCaseResponse,
CommentType,
CasePatchRequest,
} from '../../../../case/common/api';
import { ToasterError } from '../../common/components/toasters';
import { AllCases, Case } from './types';
import { AppToast, ToasterError } from '../../common/components/toasters';
import { AllCases, Case, UpdateByKey } from './types';
import * as i18n from './translations';

export const getTypedPayload = <T>(a: unknown): T => a as T;

Expand Down Expand Up @@ -107,3 +111,47 @@ export const decodeServiceConnectorCaseResponse = (respPushCase?: ServiceConnect
ServiceConnectorCaseResponseRt.decode(respPushCase),
fold(throwErrors(createToasterPlainError), identity)
);

const valueToUpdateIsSettings = (
key: UpdateByKey['updateKey'],
value: UpdateByKey['updateValue']
): value is CasePatchRequest['settings'] => key === 'settings';

const valueToUpdateIsStatus = (
key: UpdateByKey['updateKey'],
value: UpdateByKey['updateValue']
): value is CasePatchRequest['status'] => key === 'status';

export const createUpdateSuccessToaster = (
caseBeforeUpdate: Case,
caseAfterUpdate: Case,
key: UpdateByKey['updateKey'],
value: UpdateByKey['updateValue']
): AppToast => {
const caseHasAlerts = caseBeforeUpdate.comments.some(
(comment) => comment.type === CommentType.alert
);

const toast: AppToast = {
id: uuid.v4(),
color: 'success',
iconType: 'check',
title: i18n.UPDATED_CASE(caseAfterUpdate.title),
};

if (valueToUpdateIsSettings(key, value) && value?.syncAlerts && caseHasAlerts) {
return {
...toast,
title: i18n.SYNC_CASE(caseAfterUpdate.title),
};
}

if (valueToUpdateIsStatus(key, value) && caseHasAlerts && caseBeforeUpdate.settings.syncAlerts) {
return {
...toast,
text: i18n.STATUS_CHANGED_TOASTER_TEXT,
};
}

return toast;
};

0 comments on commit 12dba72

Please sign in to comment.