Skip to content

Commit

Permalink
Merge branch 'main' into retaining.api.key.rules
Browse files Browse the repository at this point in the history
  • Loading branch information
szabosteve authored May 18, 2022
2 parents 49fa86b + df38c6f commit 287a22c
Show file tree
Hide file tree
Showing 49 changed files with 729 additions and 318 deletions.
44 changes: 44 additions & 0 deletions x-pack/plugins/cases/public/client/helpers/group_alerts_by_rule.ts
Original file line number Diff line number Diff line change
@@ -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> = T | null;
interface Event {
data: EventNonEcsData[];
ecs: Ecs;
}
interface EventNonEcsData {
field: string;
value?: Maybe<string[]>;
}

export const groupAlertsByRule = (items: Event[], owner: string): CaseAttachments => {
const attachmentsByRule = items.reduce<Record<string, CommentRequestAlertType>>((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);
};
6 changes: 3 additions & 3 deletions x-pack/plugins/cases/public/common/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
21 changes: 20 additions & 1 deletion x-pack/plugins/cases/public/common/use_cases_toast.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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(
() => {
Expand Down
23 changes: 19 additions & 4 deletions x-pack/plugins/cases/public/common/use_cases_toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 <button type="button" data-test-subj="open-modal" onClick={onClick} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@ import { useCreateAttachments } from '../../../containers/use_create_attachments
type AddToExistingFlyoutProps = AllCasesSelectorModalProps & {
toastTitle?: string;
toastContent?: string;
attachments?: CaseAttachments;
};

export const useCasesAddToExistingCaseModal = (props: AddToExistingFlyoutProps) => {
export const useCasesAddToExistingCaseModal = (props: AddToExistingFlyoutProps = {}) => {
const createNewCaseFlyout = useCasesAddToNewCaseFlyout({
attachments: props.attachments,
onClose: props.onClose,
// TODO there's no need for onSuccess to be async. This will be fixed
// in a follow up clean up
Expand Down Expand Up @@ -53,18 +51,17 @@ export const useCasesAddToExistingCaseModal = (props: AddToExistingFlyoutProps)
}, [dispatch]);

const handleOnRowClick = useCallback(
async (theCase?: Case) => {
async (theCase: Case | undefined, attachments: CaseAttachments) => {
// when the case is undefined in the modal
// the user clicked "create new case"
if (theCase === undefined) {
closeModal();
createNewCaseFlyout.open();
createNewCaseFlyout.open({ attachments });
return;
}

try {
// add attachments to the case
const attachments = props.attachments;
if (attachments !== undefined && attachments.length > 0) {
await createAttachments({
caseId: theCase.id,
Expand All @@ -74,7 +71,7 @@ export const useCasesAddToExistingCaseModal = (props: AddToExistingFlyoutProps)

casesToasts.showSuccessAttach({
theCase,
attachments: props.attachments,
attachments,
title: props.toastTitle,
content: props.toastContent,
});
Expand All @@ -91,22 +88,28 @@ export const useCasesAddToExistingCaseModal = (props: AddToExistingFlyoutProps)
[casesToasts, closeModal, createNewCaseFlyout, createAttachments, props]
);

const openModal = useCallback(() => {
dispatch({
type: CasesContextStoreActionsList.OPEN_ADD_TO_CASE_MODAL,
payload: {
...props,
hiddenStatuses: [CaseStatuses.closed, StatusAll],
onRowClick: handleOnRowClick,
onClose: () => {
closeModal();
if (props.onClose) {
return props.onClose();
}
const openModal = useCallback(
({ attachments }: { attachments?: CaseAttachments } = {}) => {
dispatch({
type: CasesContextStoreActionsList.OPEN_ADD_TO_CASE_MODAL,
payload: {
...props,
hiddenStatuses: [CaseStatuses.closed, StatusAll],
onRowClick: (theCase?: Case) => {
const caseAttachments = attachments ?? [];
handleOnRowClick(theCase, caseAttachments);
},
onClose: () => {
closeModal();
if (props.onClose) {
return props.onClose();
}
},
},
},
});
}, [closeModal, dispatch, handleOnRowClick, props]);
});
},
[closeModal, dispatch, handleOnRowClick, props]
);
return {
open: openModal,
close: closeModal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

/* eslint-disable react/display-name */

import { alertComment } from '../../../containers/mock';
import { renderHook } from '@testing-library/react-hooks';
import React from 'react';
import { CasesContext } from '../../cases_context';
Expand Down Expand Up @@ -41,32 +42,53 @@ describe('use cases add to new case flyout hook', () => {

it('should throw if called outside of a cases context', () => {
const { result } = renderHook(() => {
useCasesAddToNewCaseFlyout({});
useCasesAddToNewCaseFlyout();
});
expect(result.error?.message).toContain(
'useCasesContext must be used within a CasesProvider and have a defined value'
);
});

it('should dispatch the open action when invoked', () => {
it('should dispatch the open action when invoked without attachments', () => {
const { result } = renderHook(
() => {
return useCasesAddToNewCaseFlyout({});
return useCasesAddToNewCaseFlyout();
},
{ wrapper }
);
result.current.open();
expect(dispatch).toHaveBeenCalledWith(
expect.objectContaining({
type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT,
payload: expect.objectContaining({
attachments: undefined,
}),
})
);
});

it('should dispatch the open action when invoked with attachments', () => {
const { result } = renderHook(
() => {
return useCasesAddToNewCaseFlyout();
},
{ wrapper }
);
result.current.open({ attachments: [alertComment] });
expect(dispatch).toHaveBeenCalledWith(
expect.objectContaining({
type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT,
payload: expect.objectContaining({
attachments: [alertComment],
}),
})
);
});

it('should dispatch the close action when invoked', () => {
const { result } = renderHook(
() => {
return useCasesAddToNewCaseFlyout({});
return useCasesAddToNewCaseFlyout();
},
{ wrapper }
);
Expand Down
Loading

0 comments on commit 287a22c

Please sign in to comment.