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 ({