diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx
index cfbc7d255062f..54b405feeb176 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx
@@ -11,6 +11,10 @@ import { useParams } from 'react-router-dom';
import { DeleteTimelineModalOverlay } from '.';
import { TimelineType } from '../../../../../common/types/timeline';
+import * as i18n from '../translations';
+import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
+
+jest.mock('../../../../common/hooks/use_app_toasts');
jest.mock('react-router-dom', () => {
const actual = jest.requireActual('react-router-dom');
@@ -21,12 +25,19 @@ jest.mock('react-router-dom', () => {
});
describe('DeleteTimelineModal', () => {
- const savedObjectId = 'abcd';
+ const mockAddSuccess = jest.fn();
+ (useAppToasts as jest.Mock).mockReturnValue({ addSuccess: mockAddSuccess });
+
+ afterEach(() => {
+ mockAddSuccess.mockClear();
+ });
+
+ const savedObjectIds = ['abcd'];
const defaultProps = {
closeModal: jest.fn(),
deleteTimelines: jest.fn(),
isModalOpen: true,
- savedObjectIds: [savedObjectId],
+ savedObjectIds,
title: 'Privilege Escalation',
};
@@ -56,5 +67,25 @@ describe('DeleteTimelineModal', () => {
expect(wrapper.find('[data-test-subj="remove-popover"]').first().exists()).toBe(true);
});
+
+ test('it shows correct toast message on success for deleted timelines', async () => {
+ const wrapper = mountWithIntl();
+ wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click');
+
+ expect(mockAddSuccess.mock.calls[0][0].title).toEqual(
+ i18n.SUCCESSFULLY_DELETED_TIMELINES(savedObjectIds.length)
+ );
+ });
+
+ test('it shows correct toast message on success for deleted templates', async () => {
+ (useParams as jest.Mock).mockReturnValue({ tabName: TimelineType.template });
+
+ const wrapper = mountWithIntl();
+ wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click');
+
+ expect(mockAddSuccess.mock.calls[0][0].title).toEqual(
+ i18n.SUCCESSFULLY_DELETED_TIMELINE_TEMPLATES(savedObjectIds.length)
+ );
+ });
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx
index 7dde3fbe4cd2a..41e491ccc0ceb 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx
@@ -9,8 +9,13 @@ import { EuiModal } from '@elastic/eui';
import React, { useCallback } from 'react';
import { createGlobalStyle } from 'styled-components';
+import { useParams } from 'react-router-dom';
import { DeleteTimelineModal, DELETE_TIMELINE_MODAL_WIDTH } from './delete_timeline_modal';
import { DeleteTimelines } from '../types';
+import { TimelineType } from '../../../../../common/types/timeline';
+import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
+import * as i18n from '../translations';
+
const RemovePopover = createGlobalStyle`
div.euiPopover__panel-isOpen {
display: none;
@@ -29,19 +34,29 @@ interface Props {
*/
export const DeleteTimelineModalOverlay = React.memo(
({ deleteTimelines, isModalOpen, savedObjectIds, title, onComplete }) => {
+ const { addSuccess } = useAppToasts();
+ const { tabName: timelineType } = useParams<{ tabName: TimelineType }>();
+
const internalCloseModal = useCallback(() => {
if (onComplete != null) {
onComplete();
}
}, [onComplete]);
const onDelete = useCallback(() => {
- if (savedObjectIds != null) {
+ if (savedObjectIds.length > 0) {
deleteTimelines(savedObjectIds);
+
+ addSuccess({
+ title:
+ timelineType === TimelineType.template
+ ? i18n.SUCCESSFULLY_DELETED_TIMELINE_TEMPLATES(savedObjectIds.length)
+ : i18n.SUCCESSFULLY_DELETED_TIMELINES(savedObjectIds.length),
+ });
}
if (onComplete != null) {
onComplete();
}
- }, [deleteTimelines, savedObjectIds, onComplete]);
+ }, [deleteTimelines, savedObjectIds, onComplete, addSuccess, timelineType]);
return (
<>
{isModalOpen && }
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.test.tsx
index feb30364fba23..a273ef1df9788 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.test.tsx
@@ -6,7 +6,6 @@
*/
import React from 'react';
-import { useStateToaster } from '../../../../common/components/toasters';
import { TimelineDownloader } from './export_timeline';
import { mockSelectedTimeline } from './mocks';
@@ -16,12 +15,9 @@ import { ReactWrapper, mount } from 'enzyme';
import { waitFor } from '@testing-library/react';
import { useParams } from 'react-router-dom';
-jest.mock('../translations', () => {
- return {
- EXPORT_SELECTED: 'EXPORT_SELECTED',
- EXPORT_FILENAME: 'TIMELINE',
- };
-});
+import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
+
+jest.mock('../../../../common/hooks/use_app_toasts');
jest.mock('.', () => {
return {
@@ -38,34 +34,26 @@ jest.mock('react-router-dom', () => {
};
});
-jest.mock('../../../../common/components/toasters', () => {
- const actual = jest.requireActual('../../../../common/components/toasters');
- return {
- ...actual,
- useStateToaster: jest.fn(),
- };
-});
-
describe('TimelineDownloader', () => {
+ const mockAddSuccess = jest.fn();
+ (useAppToasts as jest.Mock).mockReturnValue({ addSuccess: mockAddSuccess });
+
let wrapper: ReactWrapper;
+ const exportedIds = ['baa20980-6301-11ea-9223-95b6d4dd806c'];
const defaultTestProps = {
- exportedIds: ['baa20980-6301-11ea-9223-95b6d4dd806c'],
+ exportedIds,
getExportedData: jest.fn(),
isEnableDownloader: true,
onComplete: jest.fn(),
};
- const mockDispatchToaster = jest.fn();
beforeEach(() => {
- (useStateToaster as jest.Mock).mockReturnValue([jest.fn(), mockDispatchToaster]);
(useParams as jest.Mock).mockReturnValue({ tabName: 'default' });
});
afterEach(() => {
- (useStateToaster as jest.Mock).mockClear();
(useParams as jest.Mock).mockReset();
-
- (mockDispatchToaster as jest.Mock).mockClear();
+ mockAddSuccess.mockClear();
});
describe('should not render a downloader', () => {
@@ -104,11 +92,12 @@ describe('TimelineDownloader', () => {
};
wrapper = mount();
+
await waitFor(() => {
wrapper.update();
- expect(mockDispatchToaster.mock.calls[0][0].title).toEqual(
- i18n.SUCCESSFULLY_EXPORTED_TIMELINES
+ expect(mockAddSuccess.mock.calls[0][0].title).toEqual(
+ i18n.SUCCESSFULLY_EXPORTED_TIMELINES(exportedIds.length)
);
});
});
@@ -124,8 +113,8 @@ describe('TimelineDownloader', () => {
await waitFor(() => {
wrapper.update();
- expect(mockDispatchToaster.mock.calls[0][0].title).toEqual(
- i18n.SUCCESSFULLY_EXPORTED_TIMELINES
+ expect(mockAddSuccess.mock.calls[0][0].title).toEqual(
+ i18n.SUCCESSFULLY_EXPORTED_TIMELINE_TEMPLATES(exportedIds.length)
);
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.tsx
index 01f18b5ad9c3d..b8b1c76ffd6d7 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.tsx
@@ -6,7 +6,6 @@
*/
import React, { useCallback } from 'react';
-import uuid from 'uuid';
import { useParams } from 'react-router-dom';
import {
@@ -14,8 +13,8 @@ import {
ExportSelectedData,
} from '../../../../common/components/generic_downloader';
import * as i18n from '../translations';
-import { useStateToaster } from '../../../../common/components/toasters';
import { TimelineType } from '../../../../../common/types/timeline';
+import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
const ExportTimeline: React.FC<{
exportedIds: string[] | undefined;
@@ -23,8 +22,8 @@ const ExportTimeline: React.FC<{
isEnableDownloader: boolean;
onComplete?: () => void;
}> = ({ onComplete, isEnableDownloader, exportedIds, getExportedData }) => {
- const [, dispatchToaster] = useStateToaster();
const { tabName: timelineType } = useParams<{ tabName: TimelineType }>();
+ const { addSuccess } = useAppToasts();
const onExportSuccess = useCallback(
(exportCount) => {
@@ -32,20 +31,15 @@ const ExportTimeline: React.FC<{
onComplete();
}
- dispatchToaster({
- type: 'addToaster',
- toast: {
- id: uuid.v4(),
- title:
- timelineType === TimelineType.template
- ? i18n.SUCCESSFULLY_EXPORTED_TIMELINE_TEMPLATES(exportCount)
- : i18n.SUCCESSFULLY_EXPORTED_TIMELINES(exportCount),
- color: 'success',
- iconType: 'check',
- },
+ addSuccess({
+ title:
+ timelineType === TimelineType.template
+ ? i18n.SUCCESSFULLY_EXPORTED_TIMELINE_TEMPLATES(exportCount)
+ : i18n.SUCCESSFULLY_EXPORTED_TIMELINES(exportCount),
+ 'data-test-subj': 'addObjectToContainerSuccess',
});
},
- [dispatchToaster, onComplete, timelineType]
+ [addSuccess, onComplete, timelineType]
);
const onExportFailure = useCallback(() => {
if (onComplete != null) {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts
index 4858bf3ed6083..40af4514e26a3 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts
@@ -293,6 +293,20 @@ export const SUCCESSFULLY_EXPORTED_TIMELINE_TEMPLATES = (totalTimelineTemplates:
}
);
+export const SUCCESSFULLY_DELETED_TIMELINES = (totalTimelines: number) =>
+ i18n.translate('xpack.securitySolution.open.timeline.successfullyDeletedTimelinesTitle', {
+ values: { totalTimelines },
+ defaultMessage:
+ 'Successfully deleted {totalTimelines, plural, =0 {all timelines} =1 {{totalTimelines} timeline} other {{totalTimelines} timelines}}',
+ });
+
+export const SUCCESSFULLY_DELETED_TIMELINE_TEMPLATES = (totalTimelineTemplates: number) =>
+ i18n.translate('xpack.securitySolution.open.timeline.successfullyDeletedTimelineTemplatesTitle', {
+ values: { totalTimelineTemplates },
+ defaultMessage:
+ 'Successfully deleted {totalTimelineTemplates, plural, =0 {all timelines} =1 {{totalTimelineTemplates} timeline template} other {{totalTimelineTemplates} timeline templates}}',
+ });
+
export const TAB_TIMELINES = i18n.translate(
'xpack.securitySolution.timelines.components.tabs.timelinesTitle',
{