From 341f38577f6877456f1bfacaf65eceefd2b066f8 Mon Sep 17 00:00:00 2001
From: Angela Chuang <6295984+angorayc@users.noreply.github.com>
Date: Wed, 1 Jul 2020 23:46:26 +0100
Subject: [PATCH] fix export response (#70473)
* fix export response
* update unit tests
---
.../generic_downloader/index.test.tsx | 22 ++-
.../generic_downloader/translations.ts | 4 +-
.../public/timelines/containers/api.test.ts | 142 ++++++++++++++++--
.../public/timelines/containers/api.ts | 9 +-
4 files changed, 156 insertions(+), 21 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/common/components/generic_downloader/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/generic_downloader/index.test.tsx
index a70772911ba60..fb5dd915033b5 100644
--- a/x-pack/plugins/security_solution/public/common/components/generic_downloader/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/generic_downloader/index.test.tsx
@@ -4,9 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { shallow } from 'enzyme';
+import { shallow, mount } from 'enzyme';
import React from 'react';
-import { GenericDownloaderComponent } from './index';
+import { GenericDownloaderComponent, ExportSelectedData } from './index';
+import { errorToToaster } from '../toasters';
+
+jest.mock('../toasters', () => ({
+ useStateToaster: jest.fn(() => [jest.fn(), jest.fn()]),
+ errorToToaster: jest.fn(),
+}));
describe('GenericDownloader', () => {
test('renders correctly against snapshot', () => {
@@ -19,4 +25,16 @@ describe('GenericDownloader', () => {
);
expect(wrapper).toMatchSnapshot();
});
+
+ test('show toaster with correct error message if error occurrs', () => {
+ mount(
+
+ );
+ expect((errorToToaster as jest.Mock).mock.calls[0][0].title).toEqual('Failed to export data…');
+ });
});
diff --git a/x-pack/plugins/security_solution/public/common/components/generic_downloader/translations.ts b/x-pack/plugins/security_solution/public/common/components/generic_downloader/translations.ts
index 867c908bbacd3..a87dce8c81c56 100644
--- a/x-pack/plugins/security_solution/public/common/components/generic_downloader/translations.ts
+++ b/x-pack/plugins/security_solution/public/common/components/generic_downloader/translations.ts
@@ -7,8 +7,8 @@
import { i18n } from '@kbn/i18n';
export const EXPORT_FAILURE = i18n.translate(
- 'xpack.securitySolution.detectionEngine.rules.components.ruleDownloader.exportFailureTitle',
+ 'xpack.securitySolution.detectionEngine.rules.components.genericDownloader.exportFailureTitle',
{
- defaultMessage: 'Failed to export rules…',
+ defaultMessage: 'Failed to export data…',
}
);
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts
index 8a2f91d7171f7..089a428f7dfaf 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts
+++ b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts
@@ -7,10 +7,11 @@ import * as api from './api';
import { KibanaServices } from '../../common/lib/kibana';
import { TimelineType, TimelineStatus } from '../../../common/types/timeline';
import { TIMELINE_DRAFT_URL, TIMELINE_URL } from '../../../common/constants';
+import { ImportDataProps } from '../../alerts/containers/detection_engine/rules/types';
jest.mock('../../common/lib/kibana', () => {
return {
- KibanaServices: { get: jest.fn() },
+ KibanaServices: { get: jest.fn(() => ({ http: { fetch: jest.fn() } })) },
};
});
@@ -173,6 +174,7 @@ describe('persistTimeline', () => {
beforeAll(() => {
jest.resetAllMocks();
+ jest.resetModules();
(KibanaServices.get as jest.Mock).mockReturnValue({
http: {
@@ -188,10 +190,6 @@ describe('persistTimeline', () => {
});
});
- afterAll(() => {
- jest.resetAllMocks();
- });
-
test('it should create a draft timeline if given status is draft and timelineId is null', () => {
expect(postMock).toHaveBeenCalledWith(TIMELINE_DRAFT_URL, {
body: JSON.stringify({
@@ -334,6 +332,7 @@ describe('persistTimeline', () => {
beforeAll(() => {
jest.resetAllMocks();
+ jest.resetModules();
(KibanaServices.get as jest.Mock).mockReturnValue({
http: {
@@ -345,10 +344,6 @@ describe('persistTimeline', () => {
api.persistTimeline({ timelineId, timeline: importTimeline, version });
});
- afterAll(() => {
- jest.resetAllMocks();
- });
-
test('it should update timeline', () => {
expect(postMock.mock.calls[0][0]).toEqual(TIMELINE_URL);
});
@@ -474,6 +469,7 @@ describe('persistTimeline', () => {
beforeAll(() => {
jest.resetAllMocks();
+ jest.resetModules();
(KibanaServices.get as jest.Mock).mockReturnValue({
http: {
@@ -485,10 +481,6 @@ describe('persistTimeline', () => {
api.persistTimeline({ timelineId, timeline: inputTimeline, version });
});
- afterAll(() => {
- jest.resetAllMocks();
- });
-
test('it should update timeline', () => {
expect(patchMock.mock.calls[0][0]).toEqual(TIMELINE_URL);
});
@@ -506,3 +498,127 @@ describe('persistTimeline', () => {
});
});
});
+
+describe('importTimelines', () => {
+ const fileToImport = { fileToImport: {} } as ImportDataProps;
+ const fetchMock = jest.fn();
+
+ beforeAll(() => {
+ jest.resetAllMocks();
+ jest.resetModules();
+
+ (KibanaServices.get as jest.Mock).mockReturnValue({
+ http: {
+ fetch: fetchMock,
+ },
+ });
+ api.importTimelines(fileToImport);
+ });
+
+ test('should pass correct args to KibanaServices - url', () => {
+ expect(fetchMock.mock.calls[0][0]).toEqual('/api/timeline/_import');
+ });
+
+ test('should pass correct args to KibanaServices - args', () => {
+ expect(JSON.stringify(fetchMock.mock.calls[0][1])).toEqual(
+ JSON.stringify({
+ method: 'POST',
+ headers: { 'Content-Type': undefined },
+ body: new FormData(),
+ signal: undefined,
+ })
+ );
+ });
+});
+
+describe('exportSelectedTimeline', () => {
+ const ids = ['123', 'abc'];
+ const fetchMock = jest.fn();
+
+ beforeAll(() => {
+ jest.resetAllMocks();
+ jest.resetModules();
+
+ (KibanaServices.get as jest.Mock).mockReturnValue({
+ http: {
+ fetch: fetchMock,
+ },
+ });
+ api.exportSelectedTimeline({
+ filename: 'timelines_export.ndjson',
+ ids,
+ signal: {} as AbortSignal,
+ });
+ });
+
+ test('should pass correct args to KibanaServices', () => {
+ expect(fetchMock).toBeCalledWith('/api/timeline/_export', {
+ body: JSON.stringify({ ids }),
+ method: 'POST',
+ query: { file_name: 'timelines_export.ndjson' },
+ signal: {},
+ });
+ });
+});
+
+describe('getDraftTimeline', () => {
+ const timelineType = { timelineType: TimelineType.default };
+ const getMock = jest.fn();
+
+ beforeAll(() => {
+ jest.resetAllMocks();
+ jest.resetModules();
+
+ (KibanaServices.get as jest.Mock).mockReturnValue({
+ http: {
+ get: getMock,
+ },
+ });
+ api.getDraftTimeline(timelineType);
+ });
+
+ test('should pass correct args to KibanaServices', () => {
+ expect(getMock).toBeCalledWith('/api/timeline/_draft', {
+ query: timelineType,
+ });
+ });
+});
+
+describe('cleanDraftTimeline', () => {
+ const postMock = jest.fn();
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ jest.resetModules();
+
+ (KibanaServices.get as jest.Mock).mockReturnValue({
+ http: {
+ post: postMock,
+ },
+ });
+ });
+
+ test('should pass correct args to KibanaServices - timeline', () => {
+ const args = { timelineType: TimelineType.default };
+
+ api.cleanDraftTimeline(args);
+
+ expect(postMock).toBeCalledWith('/api/timeline/_draft', {
+ body: JSON.stringify(args),
+ });
+ });
+
+ test('should pass correct args to KibanaServices - timeline template', () => {
+ const args = {
+ timelineType: TimelineType.template,
+ templateTimelineId: 'test-123',
+ templateTimelineVersion: 1,
+ };
+
+ api.cleanDraftTimeline(args);
+
+ expect(postMock).toBeCalledWith('/api/timeline/_draft', {
+ body: JSON.stringify(args),
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.ts
index fbd89268880db..ff252ea93039d 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/api.ts
+++ b/x-pack/plugins/security_solution/public/timelines/containers/api.ts
@@ -132,6 +132,7 @@ export const persistTimeline = async ({
export const importTimelines = async ({
fileToImport,
+ signal,
}: ImportDataProps): Promise => {
const formData = new FormData();
formData.append('file', fileToImport);
@@ -140,24 +141,24 @@ export const importTimelines = async ({
method: 'POST',
headers: { 'Content-Type': undefined },
body: formData,
+ signal,
});
};
-export const exportSelectedTimeline: ExportSelectedData = async ({
+export const exportSelectedTimeline: ExportSelectedData = ({
filename = `timelines_export.ndjson`,
ids = [],
signal,
}): Promise => {
const body = ids.length > 0 ? JSON.stringify({ ids }) : undefined;
- const response = await KibanaServices.get().http.fetch<{ body: Blob }>(`${TIMELINE_EXPORT_URL}`, {
+ return KibanaServices.get().http.fetch(`${TIMELINE_EXPORT_URL}`, {
method: 'POST',
body,
query: {
file_name: filename,
},
+ signal,
});
-
- return response.body;
};
export const getDraftTimeline = async ({