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

[Cases] Add to new and existing cases bulk actions in the timelines, security_solution and o11y #130958

Merged
merged 84 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from 69 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
5c66f0b
Wip show cases bulk actions
Apr 26, 2022
4c39e39
WIP
Apr 26, 2022
577d722
Merge remote-tracking branch 'upstream/main' into feature/security-bu…
Apr 28, 2022
d31ba6b
Use custom bulk actions
Apr 28, 2022
5472535
new use add bulk hook
Apr 28, 2022
9a7f866
Close the popover if isOpen
Apr 28, 2022
08a3491
Use translations
Apr 28, 2022
d6c2f84
Merge remote-tracking branch 'upstream/main' into feature/security-bu…
Apr 28, 2022
a1b263c
Disable cases bulk actions when a query is present
Apr 28, 2022
65f1988
Allow to hide custom bulk actions when in query mode
Apr 28, 2022
6f1af3e
Send all cases attachments as single attachments
Apr 28, 2022
7ec01d8
Enhance hooks tests to include attachments
Apr 28, 2022
1d0ab47
Rename bulk action hook
Apr 28, 2022
a80e940
Fix missed spot
Apr 28, 2022
fa14e8c
Rename prop
Apr 28, 2022
e2c4f3f
Rename bulk actions component
Apr 28, 2022
5dfd3b7
Merge remote-tracking branch 'upstream/main' into feature/security-bu…
Apr 29, 2022
e7a1416
Merge remote-tracking branch 'upstream/main' into feature/security-bu…
Apr 29, 2022
7200f9c
Validate permissions to show alert status actions
Apr 29, 2022
3a79fd0
Validate permissions to show actions
Apr 29, 2022
3241cbd
Merge branch 'main' into feature/security-bulk-add-alerts-cases
academo May 2, 2022
865610a
Merge branch 'main' into feature/security-bulk-add-alerts-cases
academo May 3, 2022
3afeb35
[Discover] Unskip graph security flaky tests (#131252)
jughosta May 3, 2022
4fa3c3d
[Vizualize][Lens] Sync tooltips on dashboard level (#130449)
stratoula May 3, 2022
712a18d
[Cloud Posture] refactor findings layout (#131298)
orouz May 3, 2022
d74786e
[Synthetics] Remove buffer usage (#131319)
shahzad31 May 3, 2022
39da49d
[Cases] Introduce tabs in the case view page. (#130789)
academo May 3, 2022
d90a625
[Cases] Add MTTR metric (#130459)
cnasikas May 3, 2022
853eb8d
fix timezone (#131213)
flash1293 May 3, 2022
24496bd
Add refresh option to SavedObjectsImporter (#131339)
joshdover May 3, 2022
a10a43e
[Cloud Posture] add resource findings page (#131325)
orouz May 3, 2022
36b122b
[Lens] remove warnings when running the tests (#131373)
mbondyra May 3, 2022
52b1f02
Fix imports from restricted paths (#130102)
DianaDerevyankina May 3, 2022
0c7b98f
[Unified search] Optimise the bundle size unified search plugin (#130…
nlatipov May 3, 2022
b68fdad
[Infra] Add getMetricIndices to start contract (#131326)
miltonhultgren May 3, 2022
cf421a8
skip flaky suite (#131369)
May 3, 2022
dab2ab2
[XY] `xyVis` and `layeredXyVis`. (#128255)
Kuznietsov May 3, 2022
57752f3
[DOCS] Add April newsletter to sidenav (#131398)
thomasneirynck May 3, 2022
e68a420
[Cloud Posture] add resource findings table (#131334)
orouz May 3, 2022
587f9b3
[APM] Services without application metrics display an error (#131347)
cauemarcondes May 3, 2022
d3ab44f
Bump version caniuse-lite (#131430)
suchcodemuchwow May 3, 2022
57e04d7
changes to the `nav-kibana-dev.docnav.json` file are "docs only" (#13…
May 3, 2022
0d61e45
[Enterprise Search] Search indices empty states (#131337)
efegurkan May 3, 2022
c047b6e
[ci-stats-reporter] use a default timeout of 60 seconds (#131428)
May 3, 2022
ba010fb
Convert event log's duration from number to string in Kibana (keep as…
mikecote May 3, 2022
839d8ea
filter o11y rule aggregations (#131301)
mgiota May 3, 2022
fdae939
[Cloud Posture] Display and save rules per benchmark (#131412)
JordanSh May 3, 2022
7eaf7ad
Adding aria-label for discover data grid select document checkbox (#1…
bhavyarm May 3, 2022
eb47add
Update API docs (#130999)
thomasneirynck May 3, 2022
bd18048
[CI] Use GCS buckets for bazel remote caching (#131345)
brianseeders May 3, 2022
775d9fd
[Actionable Observability] Add license modal to rules table (#131232)
mgiota May 3, 2022
04796d9
[RAM] Add shareable rule status filter (#130705)
JiaweiWu May 3, 2022
b2245c1
[storybook] Watch for changes in packages (#131467)
clintandrewhall May 3, 2022
d12d045
Improve saved objects migrations failure errors and logs (#131359)
TinaHeiligers May 4, 2022
fb4df75
[Unified observability] Add tour step to guided setup (#131149)
May 4, 2022
7dd5525
Merge remote-tracking branch 'upstream/main' into feature/security-bu…
May 4, 2022
c1a0f05
Fix toast message for multiple alerts
May 4, 2022
a6acaaa
Fix translation label
May 4, 2022
2abf8d9
Merge remote-tracking branch 'upstream/main' into feature/security-bu…
May 4, 2022
c4f64a3
Merge remote-tracking branch 'upstream/main' into feature/security-bu…
May 10, 2022
c432512
use object.values
May 10, 2022
70a3ecf
use plural translations instead of two tags
May 10, 2022
742462c
Fix test
May 10, 2022
dd1b14a
Update hooks to only accept attachmetns on open
May 10, 2022
0599387
remove old translations
May 10, 2022
e6d7592
pass attachments as part of an object parameter
May 10, 2022
a487c94
Pass attachments in the open call for cases
May 10, 2022
3b8ef1e
fix broken test cases
May 10, 2022
390aac6
Merge branch 'main' into feature/security-bulk-add-alerts-cases
academo May 10, 2022
70c5c3a
Merge branch 'main' into feature/security-bulk-add-alerts-cases
academo May 11, 2022
9c3c167
Update x-pack/plugins/cases/public/common/translations.ts
academo May 11, 2022
ad3a06c
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine May 11, 2022
eae4b7b
Fix tests with new toast message
May 12, 2022
44055a7
Merge branch 'main' of github.com:elastic/kibana into feature/securit…
mgiota May 16, 2022
65d111b
Merge remote-tracking branch 'upstream/main' into feature/security-bu…
May 16, 2022
688536a
Merge remote-tracking branch 'upstream/main' into feature/security-bu…
May 18, 2022
90ab499
Add clarifying comment
May 18, 2022
f8429ac
do not pass an empty object
May 18, 2022
2f271f0
remove left-over test
May 18, 2022
a3e0657
rename files
May 18, 2022
1ef3761
Merge branch 'main' into feature/security-bulk-add-alerts-cases
kibanamachine May 18, 2022
54a3480
recover lost commit
May 18, 2022
87e127b
recover lost commit
May 18, 2022
cf78f1a
Remove old copy
May 18, 2022
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
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: [],
academo marked this conversation as resolved.
Show resolved Hide resolved
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)) {
academo marked this conversation as resolved.
Show resolved Hide resolved
alerts.push(item.ecs._id ?? '');
indexes.push(item.ecs._index ?? '');
academo marked this conversation as resolved.
Show resolved Hide resolved
}
return acc;
}, {});
return Object.values(attachmentsByRule);
};
7 changes: 4 additions & 3 deletions x-pack/plugins/cases/public/common/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,11 @@ 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 has been added to "{title}"',
values: { quantity, title },
defaultMessage:
'{quantity, plural, =1 {An alert has} other {Alerts have}} been added to "{title}"',
academo marked this conversation as resolved.
Show resolved Hide resolved
});

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 has been 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 have been added to "Another horrible breach!!');
});

it('should display a generic title when called with a non-alert attachament', () => {
const { result } = renderHook(
() => {
Expand Down
21 changes: 17 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,20 @@ const EuiTextStyled = styled(EuiText)`
`}
`;

function getAlertsCount(attachments: CaseAttachments): number {
let alertsCount = 0;
for (const attachment of attachments) {
if (attachment.type === CommentType.alert) {
if (Array.isArray(attachment.alertId) && attachment.alertId.length > 1) {
academo marked this conversation as resolved.
Show resolved Hide resolved
alertsCount += attachment.alertId.length;
}

alertsCount++;
academo marked this conversation as resolved.
Show resolved Hide resolved
}
}
return alertsCount;
}

function getToastTitle({
theCase,
title,
Expand All @@ -47,10 +61,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 = {}) => {
academo marked this conversation as resolved.
Show resolved Hide resolved
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({
peteharverson marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -48,7 +49,7 @@ describe('use cases add to new case flyout hook', () => {
);
});

it('should dispatch the open action when invoked', () => {
it('should dispatch the open action when invoked without attachments', () => {
const { result } = renderHook(
() => {
return useCasesAddToNewCaseFlyout({});
Expand All @@ -59,6 +60,45 @@ describe('use cases add to new case flyout hook', () => {
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 open action when invoked, with custom attachments', () => {
academo marked this conversation as resolved.
Show resolved Hide resolved
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],
}),
})
);
});
Expand Down
Loading