diff --git a/x-pack/plugins/cases/public/client/helpers/group_alerts_by_rule.ts b/x-pack/plugins/cases/public/client/helpers/group_alerts_by_rule.ts new file mode 100644 index 0000000000000..0c1d73ba03b71 --- /dev/null +++ b/x-pack/plugins/cases/public/client/helpers/group_alerts_by_rule.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CommentRequestAlertType } from '../../../common/api'; +import { CommentType, Ecs } from '../../../common'; +import { getRuleIdFromEvent } from './get_rule_id_from_event'; +import { CaseAttachments } from '../../types'; + +type Maybe = T | null; +interface Event { + data: EventNonEcsData[]; + ecs: Ecs; +} +interface EventNonEcsData { + field: string; + value?: Maybe; +} + +export const groupAlertsByRule = (items: Event[], owner: string): CaseAttachments => { + const attachmentsByRule = items.reduce>((acc, item) => { + const rule = getRuleIdFromEvent(item); + if (!acc[rule.id]) { + acc[rule.id] = { + alertId: [], + index: [], + owner, + type: CommentType.alert as const, + rule, + }; + } + const alerts = acc[rule.id].alertId; + const indexes = acc[rule.id].index; + if (Array.isArray(alerts) && Array.isArray(indexes)) { + alerts.push(item.ecs._id ?? ''); + indexes.push(item.ecs._index ?? ''); + } + return acc; + }, {}); + return Object.values(attachmentsByRule); +}; diff --git a/x-pack/plugins/cases/public/common/translations.ts b/x-pack/plugins/cases/public/common/translations.ts index 908a0dd5d52df..9f81b8a7e98ee 100644 --- a/x-pack/plugins/cases/public/common/translations.ts +++ b/x-pack/plugins/cases/public/common/translations.ts @@ -264,10 +264,10 @@ export const CASE_SUCCESS_TOAST = (title: string) => defaultMessage: '{title} has been updated', }); -export const CASE_ALERT_SUCCESS_TOAST = (title: string) => +export const CASE_ALERT_SUCCESS_TOAST = (title: string, quantity: number = 1) => i18n.translate('xpack.cases.actions.caseAlertSuccessToast', { - values: { title }, - defaultMessage: 'An alert was added to "{title}"', + values: { quantity, title }, + defaultMessage: '{quantity, plural, =1 {An alert was} other {Alerts were}} added to "{title}"', }); export const CASE_ALERT_SUCCESS_SYNC_TEXT = i18n.translate( diff --git a/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx b/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx index e788f7b399bb4..c764df4d6661d 100644 --- a/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx @@ -72,7 +72,7 @@ describe('Use cases toast hook', () => { validateTitle('Custom title'); }); - it('should display the alert sync title when called with an alert attachment ', () => { + it('should display the alert sync title when called with an alert attachment (1 alert)', () => { const { result } = renderHook( () => { return useCasesToast(); @@ -86,6 +86,25 @@ describe('Use cases toast hook', () => { validateTitle('An alert was added to "Another horrible breach!!'); }); + it('should display the alert sync title when called with an alert attachment (multiple alerts)', () => { + const { result } = renderHook( + () => { + return useCasesToast(); + }, + { wrapper: TestProviders } + ); + const alert = { + ...alertComment, + alertId: ['1234', '54321'], + } as SupportedCaseAttachment; + + result.current.showSuccessAttach({ + theCase: mockCase, + attachments: [alert], + }); + validateTitle('Alerts were added to "Another horrible breach!!'); + }); + it('should display a generic title when called with a non-alert attachament', () => { const { result } = renderHook( () => { diff --git a/x-pack/plugins/cases/public/common/use_cases_toast.tsx b/x-pack/plugins/cases/public/common/use_cases_toast.tsx index 8abb18d3099f3..36ca8340036a5 100644 --- a/x-pack/plugins/cases/public/common/use_cases_toast.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_toast.tsx @@ -34,6 +34,22 @@ const EuiTextStyled = styled(EuiText)` `} `; +function getAlertsCount(attachments: CaseAttachments): number { + let alertsCount = 0; + for (const attachment of attachments) { + if (attachment.type === CommentType.alert) { + // alertId might be an array + if (Array.isArray(attachment.alertId) && attachment.alertId.length > 1) { + alertsCount += attachment.alertId.length; + } else { + // or might be a single string + alertsCount++; + } + } + } + return alertsCount; +} + function getToastTitle({ theCase, title, @@ -47,10 +63,9 @@ function getToastTitle({ return title; } if (attachments !== undefined) { - for (const attachment of attachments) { - if (attachment.type === CommentType.alert) { - return CASE_ALERT_SUCCESS_TOAST(theCase.title); - } + const alertsCount = getAlertsCount(attachments); + if (alertsCount > 0) { + return CASE_ALERT_SUCCESS_TOAST(theCase.title, alertsCount); } } return CASE_SUCCESS_TOAST(theCase.title); diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx index f0eea39d551a7..822bbdee80a94 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx @@ -15,7 +15,6 @@ import { AppMockRenderer, createAppMockRenderer } from '../../../common/mock'; import { useCasesToast } from '../../../common/use_cases_toast'; import { alertComment } from '../../../containers/mock'; import { useCreateAttachments } from '../../../containers/use_create_attachments'; -import { SupportedCaseAttachment } from '../../../types'; import { CasesContext } from '../../cases_context'; import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer'; import { useCasesAddToExistingCaseModal } from './use_cases_add_to_existing_case_modal'; @@ -35,12 +34,10 @@ const AllCasesSelectorModalMock = AllCasesSelectorModal as unknown as jest.Mock; // test component to test the hook integration const TestComponent: React.FC = () => { - const hook = useCasesAddToExistingCaseModal({ - attachments: [alertComment as SupportedCaseAttachment], - }); + const hook = useCasesAddToExistingCaseModal(); const onClick = () => { - hook.open(); + hook.open({ attachments: [alertComment] }); }; return