From ca5623663f98dfe4ec715974a6c9077a46403eb1 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Mar 2020 09:10:25 +0000 Subject: [PATCH 1/7] allow users importing data if they are authorized --- .../public/components/open_timeline/index.tsx | 10 +++++----- .../components/open_timeline/open_timeline.tsx | 18 +++++++++--------- .../public/components/open_timeline/types.ts | 4 ++-- .../public/pages/timelines/timelines_page.tsx | 16 +++++++++++----- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx index 6c2cd21d808b7..c27a6039da29d 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx @@ -54,7 +54,7 @@ interface OwnProps { export type OpenTimelineOwnProps = OwnProps & Pick< OpenTimelineProps, - 'defaultPageSize' | 'title' | 'importCompleteToggle' | 'setImportCompleteToggle' + 'defaultPageSize' | 'title' | 'importDataModalToggle' | 'setImportDataModalToggle' > & PropsFromRedux; @@ -77,9 +77,9 @@ export const StatefulOpenTimelineComponent = React.memo( defaultPageSize, hideActions = [], isModal = false, - importCompleteToggle, + importDataModalToggle, onOpenTimeline, - setImportCompleteToggle, + setImportDataModalToggle, timeline, title, updateTimeline, @@ -269,7 +269,7 @@ export const StatefulOpenTimelineComponent = React.memo( defaultPageSize={defaultPageSize} isLoading={loading} itemIdToExpandedNotesRowMap={itemIdToExpandedNotesRowMap} - importCompleteToggle={importCompleteToggle} + importDataModalToggle={importDataModalToggle} onAddTimelinesToFavorites={undefined} onDeleteSelected={onDeleteSelected} onlyFavorites={onlyFavorites} @@ -284,7 +284,7 @@ export const StatefulOpenTimelineComponent = React.memo( query={search} refetch={refetch} searchResults={timelines} - setImportCompleteToggle={setImportCompleteToggle} + setImportDataModalToggle={setImportDataModalToggle} selectedItems={selectedItems} sortDirection={sortDirection} sortField={sortField} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx index 8b3da4427a362..6b2f953b82de4 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx @@ -33,7 +33,7 @@ export const OpenTimeline = React.memo( defaultPageSize, isLoading, itemIdToExpandedNotesRowMap, - importCompleteToggle, + importDataModalToggle, onAddTimelinesToFavorites, onDeleteSelected, onlyFavorites, @@ -50,7 +50,7 @@ export const OpenTimeline = React.memo( searchResults, selectedItems, sortDirection, - setImportCompleteToggle, + setImportDataModalToggle, sortField, title, totalSearchResultsCount, @@ -103,18 +103,18 @@ export const OpenTimeline = React.memo( }, [refetch]); const handleCloseModal = useCallback(() => { - if (setImportCompleteToggle != null) { - setImportCompleteToggle(false); + if (setImportDataModalToggle != null) { + setImportDataModalToggle(false); } - }, [setImportCompleteToggle]); + }, [setImportDataModalToggle]); const handleComplete = useCallback(() => { - if (setImportCompleteToggle != null) { - setImportCompleteToggle(false); + if (setImportDataModalToggle != null) { + setImportDataModalToggle(false); } if (refetch != null) { refetch(); } - }, [setImportCompleteToggle, refetch]); + }, [setImportDataModalToggle, refetch]); return ( <> @@ -136,7 +136,7 @@ export const OpenTimeline = React.memo( importData={importTimelines} successMessage={i18n.SUCCESSFULLY_IMPORTED_TIMELINES} showCheckBox={false} - showModal={importCompleteToggle ?? false} + showModal={importDataModalToggle ?? false} submitBtnText={i18n.IMPORT_TIMELINE_BTN_TITLE} subtitle={i18n.INITIAL_PROMPT_TEXT} title={i18n.IMPORT_TIMELINE} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts index 1265c056ec506..51c72681c0863 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts @@ -121,7 +121,7 @@ export interface OpenTimelineProps { /** Required by EuiTable for expandable rows: a map of `TimelineResult.savedObjectId` to rendered notes */ itemIdToExpandedNotesRowMap: Record; /** Display import timelines modal*/ - importCompleteToggle?: boolean; + importDataModalToggle?: boolean; /** If this callback is specified, a "Favorite Selected" button will be displayed, and this callback will be invoked when the button is clicked */ onAddTimelinesToFavorites?: OnAddTimelinesToFavorites; /** If this callback is specified, a "Delete Selected" button will be displayed, and this callback will be invoked when the button is clicked */ @@ -153,7 +153,7 @@ export interface OpenTimelineProps { /** the currently-selected timelines in the table */ selectedItems: OpenTimelineResult[]; /** Toggle export timelines modal*/ - setImportCompleteToggle?: React.Dispatch>; + setImportDataModalToggle?: React.Dispatch>; /** the requested sort direction of the query results */ sortDirection: 'asc' | 'desc'; /** the requested field to sort on */ diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx index 38462e6526454..27ad1fbbb3efc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx @@ -14,6 +14,7 @@ import { StatefulOpenTimeline } from '../../components/open_timeline'; import { WrapperPage } from '../../components/wrapper_page'; import { SpyRoute } from '../../utils/route/spy_routes'; import * as i18n from './translations'; +import { useKibana } from '../../lib/kibana'; const TimelinesContainer = styled.div` width: 100%; @@ -28,10 +29,15 @@ type OwnProps = TimelinesProps; export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; const TimelinesPageComponent: React.FC = ({ apolloClient }) => { - const [importCompleteToggle, setImportCompleteToggle] = useState(false); + const [importDataModalToggle, setImportDataModalToggle] = useState(false); const onImportTimelineBtnClick = useCallback(() => { - setImportCompleteToggle(true); - }, [setImportCompleteToggle]); + setImportDataModalToggle(true); + }, [setImportDataModalToggle]); + + const uiCapabilities = useKibana().services.application.capabilities; + const capabilitiesCanUserCRUD: boolean = + typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false; + return ( <> @@ -46,8 +52,8 @@ const TimelinesPageComponent: React.FC = ({ apolloClient }) => { apolloClient={apolloClient} defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} isModal={false} - importCompleteToggle={importCompleteToggle} - setImportCompleteToggle={setImportCompleteToggle} + importDataModalToggle={importDataModalToggle && capabilitiesCanUserCRUD} + setImportDataModalToggle={setImportDataModalToggle} title={i18n.ALL_TIMELINES_PANEL_TITLE} /> From bd15900b77177383afada9acce7185670a3e73df Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Mar 2020 11:51:25 +0000 Subject: [PATCH 2/7] rename props --- .../siem/public/components/import_data_modal/index.tsx | 8 ++++---- .../siem/public/containers/detection_engine/rules/api.ts | 4 ++-- .../public/containers/detection_engine/rules/types.ts | 2 +- .../plugins/siem/public/containers/timeline/all/api.ts | 4 ++-- .../api_integration/apis/siem/saved_objects/timeline.ts | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx b/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx index 503710f1ee8aa..70e2ff9ddc906 100644 --- a/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import React, { useCallback, useState } from 'react'; -import { ImportRulesResponse, ImportRulesProps } from '../../containers/detection_engine/rules'; +import { ImportRulesResponse, ImportDataProps } from '../../containers/detection_engine/rules'; import { displayErrorToast, displaySuccessToast, @@ -37,7 +37,7 @@ interface ImportDataModalProps { errorMessage: string; failedDetailed: (id: string, statusCode: number, message: string) => string; importComplete: () => void; - importData: (arg: ImportRulesProps) => Promise; + importData: (arg: ImportDataProps) => Promise; showCheckBox: boolean; showModal: boolean; submitBtnText: string; @@ -75,7 +75,7 @@ export const ImportDataModalComponent = ({ closeModal(); }, [setIsImporting, setSelectedFiles, closeModal]); - const importRulesCallback = useCallback(async () => { + const importDataCallback = useCallback(async () => { if (selectedFiles != null) { setIsImporting(true); const abortCtrl = new AbortController(); @@ -152,7 +152,7 @@ export const ImportDataModalComponent = ({ {i18n.CANCEL_BUTTON} diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index 4b0e0030be53d..a53752c6ca0b5 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -15,7 +15,7 @@ import { Rule, FetchRuleProps, BasicFetchProps, - ImportRulesProps, + ImportDataProps, ExportDocumentsProps, RuleStatusResponse, ImportRulesResponse, @@ -204,7 +204,7 @@ export const importRules = async ({ fileToImport, overwrite = false, signal, -}: ImportRulesProps): Promise => { +}: ImportDataProps): Promise => { const formData = new FormData(); formData.append('file', fileToImport); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts index 53a1c0770028c..a721e064382b8 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts @@ -194,7 +194,7 @@ export interface BasicFetchProps { signal: AbortSignal; } -export interface ImportRulesProps { +export interface ImportDataProps { fileToImport: File; overwrite?: boolean; signal: AbortSignal; diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts b/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts index 0479851fc5b55..3c17e489fed3e 100644 --- a/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ImportRulesProps, ImportRulesResponse } from '../../detection_engine/rules'; +import { ImportDataProps, ImportRulesResponse } from '../../detection_engine/rules'; import { KibanaServices } from '../../../lib/kibana'; import { TIMELINE_IMPORT_URL, TIMELINE_EXPORT_URL } from '../../../../common/constants'; import { ExportSelectedData } from '../../../components/generic_downloader'; @@ -13,7 +13,7 @@ export const importTimelines = async ({ fileToImport, overwrite = false, signal, -}: ImportRulesProps): Promise => { +}: ImportDataProps): Promise => { const formData = new FormData(); formData.append('file', fileToImport); diff --git a/x-pack/test/api_integration/apis/siem/saved_objects/timeline.ts b/x-pack/test/api_integration/apis/siem/saved_objects/timeline.ts index a7e7cf4476f3f..6fe11bc294795 100644 --- a/x-pack/test/api_integration/apis/siem/saved_objects/timeline.ts +++ b/x-pack/test/api_integration/apis/siem/saved_objects/timeline.ts @@ -175,7 +175,7 @@ export default function({ getService }: FtrProviderContext) { expect(version).to.not.be.empty(); }); - it.skip('Update a timeline with a new title', async () => { + it('Update a timeline with a new title', async () => { const titleToSaved = 'hello title'; const response = await createBasicTimeline(client, titleToSaved); const { savedObjectId, version } = response.data && response.data.persistTimeline.timeline; From 941ae21d28c80a31baa5d28fb5177834db928bf0 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Mar 2020 11:56:32 +0000 Subject: [PATCH 3/7] rename types --- .../siem/public/components/import_data_modal/index.tsx | 4 ++-- .../siem/public/containers/detection_engine/rules/api.ts | 6 +++--- .../siem/public/containers/detection_engine/rules/types.ts | 2 +- .../plugins/siem/public/containers/timeline/all/api.ts | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx b/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx index 70e2ff9ddc906..c827411a41e2e 100644 --- a/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/import_data_modal/index.tsx @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import React, { useCallback, useState } from 'react'; -import { ImportRulesResponse, ImportDataProps } from '../../containers/detection_engine/rules'; +import { ImportDataResponse, ImportDataProps } from '../../containers/detection_engine/rules'; import { displayErrorToast, displaySuccessToast, @@ -37,7 +37,7 @@ interface ImportDataModalProps { errorMessage: string; failedDetailed: (id: string, statusCode: number, message: string) => string; importComplete: () => void; - importData: (arg: ImportDataProps) => Promise; + importData: (arg: ImportDataProps) => Promise; showCheckBox: boolean; showModal: boolean; submitBtnText: string; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index a53752c6ca0b5..2dd6955581eff 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -18,7 +18,7 @@ import { ImportDataProps, ExportDocumentsProps, RuleStatusResponse, - ImportRulesResponse, + ImportDataResponse, PrePackagedRulesStatusResponse, BulkRuleResponse, } from './types'; @@ -204,11 +204,11 @@ export const importRules = async ({ fileToImport, overwrite = false, signal, -}: ImportDataProps): Promise => { +}: ImportDataProps): Promise => { const formData = new FormData(); formData.append('file', fileToImport); - return KibanaServices.get().http.fetch( + return KibanaServices.get().http.fetch( `${DETECTION_ENGINE_RULES_URL}/_import`, { method: 'POST', diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts index a721e064382b8..f676ab944fce4 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts @@ -208,7 +208,7 @@ export interface ImportRulesResponseError { }; } -export interface ImportRulesResponse { +export interface ImportDataResponse { success: boolean; success_count: number; errors: ImportRulesResponseError[]; diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts b/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts index 3c17e489fed3e..4c8e2384de585 100644 --- a/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ImportDataProps, ImportRulesResponse } from '../../detection_engine/rules'; +import { ImportDataProps, ImportDataResponse } from '../../detection_engine/rules'; import { KibanaServices } from '../../../lib/kibana'; import { TIMELINE_IMPORT_URL, TIMELINE_EXPORT_URL } from '../../../../common/constants'; import { ExportSelectedData } from '../../../components/generic_downloader'; @@ -13,11 +13,11 @@ export const importTimelines = async ({ fileToImport, overwrite = false, signal, -}: ImportDataProps): Promise => { +}: ImportDataProps): Promise => { const formData = new FormData(); formData.append('file', fileToImport); - return KibanaServices.get().http.fetch(`${TIMELINE_IMPORT_URL}`, { + return KibanaServices.get().http.fetch(`${TIMELINE_IMPORT_URL}`, { method: 'POST', headers: { 'Content-Type': undefined }, query: { overwrite }, From fb57529e250647470eb7b1ffcf25fb0a4f2fb675 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Mar 2020 17:55:17 +0000 Subject: [PATCH 4/7] hide import timeline btn if unauthorized --- .../timelines_table/actions_columns.test.tsx | 68 +++++++++++++++++++ .../timelines_table/actions_columns.tsx | 1 + .../public/pages/timelines/timelines_page.tsx | 8 ++- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx index ca82e30798d82..8805037ecc4ca 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx @@ -156,4 +156,72 @@ describe('#getActionsColumns', () => { expect(onOpenTimeline).toBeCalledWith({ duplicate: true, timelineId: 'saved-timeline-11' }); }); + + test('it renders the export icon when enableExportTimelineDownloader is including the action export', () => { + const testProps: TimelinesTableProps = { + ...getMockTimelinesTableProps(mockResults), + actionTimelineToShow: ['export'], + }; + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.find('[data-test-subj="export-timeline"]').exists()).toBe(true); + }); + + test('it renders No export icon when export is not included in the action ', () => { + const testProps: TimelinesTableProps = { + ...getMockTimelinesTableProps(mockResults), + }; + const wrapper = mountWithIntl( + + + + ); + expect(wrapper.find('[data-test-subj="export-timeline"]').exists()).toBe(false); + }); + + test('it renders a disabled the export button if the timeline does not have a saved object id', () => { + const missingSavedObjectId: OpenTimelineResult[] = [ + omit('savedObjectId', { ...mockResults[0] }), + ]; + + const testProps: TimelinesTableProps = { + ...getMockTimelinesTableProps(missingSavedObjectId), + actionTimelineToShow: ['export'], + }; + const wrapper = mountWithIntl( + + + + ); + + const props = wrapper + .find('[data-test-subj="export-timeline"]') + .first() + .props() as EuiButtonIconProps; + expect(props.isDisabled).toBe(true); + }); + + test('it invokes enableExportTimelineDownloader with the expected params when the button is clicked', () => { + const enableExportTimelineDownloader = jest.fn(); + const testProps: TimelinesTableProps = { + ...getMockTimelinesTableProps(mockResults), + actionTimelineToShow: ['export'], + enableExportTimelineDownloader, + }; + const wrapper = mountWithIntl( + + + + ); + + wrapper + .find('[data-test-subj="export-timeline"]') + .first() + .simulate('click'); + + expect(enableExportTimelineDownloader).toBeCalledWith(mockResults[0]); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx index 4bbf98dafe38d..8588beed64b79 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx @@ -55,6 +55,7 @@ export const getActionsColumns = ({ }, enabled: ({ savedObjectId }: OpenTimelineResult) => savedObjectId != null, description: i18n.EXPORT_SELECTED, + 'data-test-subj': 'export-timeline', }; const deleteTimelineColumn = { diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx index 27ad1fbbb3efc..75bef7a04a4c9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx @@ -42,9 +42,11 @@ const TimelinesPageComponent: React.FC = ({ apolloClient }) => { <> - - {i18n.ALL_TIMELINES_IMPORT_TIMELINE_TITLE} - + {capabilitiesCanUserCRUD && ( + + {i18n.ALL_TIMELINES_IMPORT_TIMELINE_TITLE} + + )} From be41b8808590f019071bb3090615ecc6b109a3a9 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Mar 2020 19:55:16 +0000 Subject: [PATCH 5/7] unit test for TimelinesPageComponent --- .../pages/timelines/timelines_page.test.tsx | 89 +++++++++++++++++++ .../public/pages/timelines/timelines_page.tsx | 9 +- 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx new file mode 100644 index 0000000000000..62399891c9606 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.test.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TimelinesPageComponent } from './timelines_page'; +import { useKibana } from '../../lib/kibana'; +import { shallow, ShallowWrapper } from 'enzyme'; +import React from 'react'; +import ApolloClient from 'apollo-client'; + +jest.mock('../../lib/kibana', () => { + return { + useKibana: jest.fn(), + }; +}); +describe('TimelinesPageComponent', () => { + const mockAppollloClient = {} as ApolloClient; + let wrapper: ShallowWrapper; + + describe('If the user is authorised', () => { + beforeAll(() => { + ((useKibana as unknown) as jest.Mock).mockReturnValue({ + services: { + application: { + capabilities: { + siem: { + crud: true, + }, + }, + }, + }, + }); + wrapper = shallow(); + }); + + afterAll(() => { + ((useKibana as unknown) as jest.Mock).mockReset(); + }); + + test('should not show the import timeline modal by default', () => { + expect( + wrapper.find('[data-test-subj="stateful-open-timeline"]').prop('importDataModalToggle') + ).toEqual(false); + }); + + test('should show the import timeline button', () => { + expect(wrapper.find('[data-test-subj="open-import-data-modal-btn"]').exists()).toEqual(true); + }); + + test('should show the import timeline modal after user clicking on the button', () => { + wrapper.find('[data-test-subj="open-import-data-modal-btn"]').simulate('click'); + expect( + wrapper.find('[data-test-subj="stateful-open-timeline"]').prop('importDataModalToggle') + ).toEqual(true); + }); + }); + + describe('If the user is not authorised', () => { + beforeAll(() => { + ((useKibana as unknown) as jest.Mock).mockReturnValue({ + services: { + application: { + capabilities: { + siem: { + crud: false, + }, + }, + }, + }, + }); + wrapper = shallow(); + }); + + afterAll(() => { + ((useKibana as unknown) as jest.Mock).mockReset(); + }); + test('should not show the import timeline modal by default', () => { + expect( + wrapper.find('[data-test-subj="stateful-open-timeline"]').prop('importDataModalToggle') + ).toEqual(false); + }); + + test('should not show the import timeline button', () => { + expect(wrapper.find('[data-test-subj="open-import-data-modal-btn"]').exists()).toEqual(false); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx index 75bef7a04a4c9..73070d2b94aac 100644 --- a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx @@ -28,7 +28,7 @@ type OwnProps = TimelinesProps; export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; -const TimelinesPageComponent: React.FC = ({ apolloClient }) => { +export const TimelinesPageComponent: React.FC = ({ apolloClient }) => { const [importDataModalToggle, setImportDataModalToggle] = useState(false); const onImportTimelineBtnClick = useCallback(() => { setImportDataModalToggle(true); @@ -43,7 +43,11 @@ const TimelinesPageComponent: React.FC = ({ apolloClient }) => { {capabilitiesCanUserCRUD && ( - + {i18n.ALL_TIMELINES_IMPORT_TIMELINE_TITLE} )} @@ -57,6 +61,7 @@ const TimelinesPageComponent: React.FC = ({ apolloClient }) => { importDataModalToggle={importDataModalToggle && capabilitiesCanUserCRUD} setImportDataModalToggle={setImportDataModalToggle} title={i18n.ALL_TIMELINES_PANEL_TITLE} + data-test-subj="stateful-open-timeline" /> From bb4bd706d3f2ba5c36d3fba0044397c8f834157a Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 27 Mar 2020 13:36:44 +0000 Subject: [PATCH 6/7] update schemas --- .../lib/timeline/routes/schemas/schemas.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts index 63aee97729141..93124a20dee2e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts @@ -24,19 +24,11 @@ export const filters = Joi.array() disabled: Joi.boolean().allow(null), field: allowEmptyString, formattedValue: allowEmptyString, - index: { - type: 'keyword', - }, - key: { - type: 'keyword', - }, - negate: { - type: 'boolean', - }, + index: Joi.string(), + key: Joi.string(), + negate: Joi.boolean().allow(null), params: allowEmptyString, - type: { - type: 'keyword', - }, + type: Joi.string(), value: allowEmptyString, }), exists: allowEmptyString, From 6a1b6365d8e2f9cf2f41ee5c964f3ca526d762f8 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 30 Mar 2020 14:08:08 +0100 Subject: [PATCH 7/7] update schema --- .../lib/timeline/routes/schemas/schemas.ts | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts index 93124a20dee2e..6552f973a66fa 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/routes/schemas/schemas.ts @@ -6,14 +6,14 @@ import Joi from 'joi'; const allowEmptyString = Joi.string().allow([null, '']); -const columnHeaderType = Joi.string(); +const columnHeaderType = allowEmptyString; export const created = Joi.number().allow(null); -export const createdBy = Joi.string(); +export const createdBy = allowEmptyString; export const description = allowEmptyString; export const end = Joi.number(); export const eventId = allowEmptyString; -export const eventType = Joi.string(); +export const eventType = allowEmptyString; export const filters = Joi.array() .items( @@ -24,11 +24,11 @@ export const filters = Joi.array() disabled: Joi.boolean().allow(null), field: allowEmptyString, formattedValue: allowEmptyString, - index: Joi.string(), - key: Joi.string(), + index: allowEmptyString, + key: allowEmptyString, negate: Joi.boolean().allow(null), params: allowEmptyString, - type: Joi.string(), + type: allowEmptyString, value: allowEmptyString, }), exists: allowEmptyString, @@ -60,22 +60,22 @@ export const version = allowEmptyString; export const columns = Joi.array().items( Joi.object({ aggregatable: Joi.boolean().allow(null), - category: Joi.string(), + category: allowEmptyString, columnHeaderType, description, example: allowEmptyString, indexes: allowEmptyString, - id: Joi.string(), + id: allowEmptyString, name, placeholder: allowEmptyString, searchable: Joi.boolean().allow(null), - type: Joi.string(), + type: allowEmptyString, }).required() ); export const dataProviders = Joi.array() .items( Joi.object({ - id: Joi.string(), + id: allowEmptyString, name: allowEmptyString, enabled: Joi.boolean().allow(null), excluded: Joi.boolean().allow(null), @@ -90,7 +90,7 @@ export const dataProviders = Joi.array() and: Joi.array() .items( Joi.object({ - id: Joi.string(), + id: allowEmptyString, name, enabled: Joi.boolean().allow(null), excluded: Joi.boolean().allow(null), @@ -114,9 +114,9 @@ export const dateRange = Joi.object({ }); export const favorite = Joi.array().items( Joi.object({ - keySearch: Joi.string(), - fullName: Joi.string(), - userName: Joi.string(), + keySearch: allowEmptyString, + fullName: allowEmptyString, + userName: allowEmptyString, favoriteDate: Joi.number(), }).allow(null) ); @@ -133,26 +133,26 @@ const noteItem = Joi.object({ }); export const eventNotes = Joi.array().items(noteItem); export const globalNotes = Joi.array().items(noteItem); -export const kqlMode = Joi.string(); +export const kqlMode = allowEmptyString; export const kqlQuery = Joi.object({ filterQuery: Joi.object({ kuery: Joi.object({ - kind: Joi.string(), + kind: allowEmptyString, expression: allowEmptyString, }), serializedQuery: allowEmptyString, }), }); export const pinnedEventIds = Joi.array() - .items(Joi.string()) + .items(allowEmptyString) .allow(null); export const sort = Joi.object({ - columnId: Joi.string(), - sortDirection: Joi.string(), + columnId: allowEmptyString, + sortDirection: allowEmptyString, }); /* eslint-disable @typescript-eslint/camelcase */ -export const ids = Joi.array().items(Joi.string()); +export const ids = Joi.array().items(allowEmptyString); export const exclude_export_details = Joi.boolean(); export const file_name = allowEmptyString;