mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[SecuritySolution] Add success toast to timeline deletion (#99612)
* Add success toast to timeline deletion * Add unit tests for timeline deletion toast * Refactor export_timeline to use useAppToasts instead of useStateToaster
This commit is contained in:
parent
5f618da802
commit
0ffe4c7a54
5 changed files with 87 additions and 44 deletions
|
@ -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(<DeleteTimelineModalOverlay {...defaultProps} />);
|
||||
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(<DeleteTimelineModalOverlay {...defaultProps} />);
|
||||
wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click');
|
||||
|
||||
expect(mockAddSuccess.mock.calls[0][0].title).toEqual(
|
||||
i18n.SUCCESSFULLY_DELETED_TIMELINE_TEMPLATES(savedObjectIds.length)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<Props>(
|
||||
({ 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 && <RemovePopover data-test-subj="remove-popover" />}
|
||||
|
|
|
@ -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(<TimelineDownloader {...testProps} />);
|
||||
|
||||
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)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue