diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts index 24325676f0cca0..30a26ad9d18ea9 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts @@ -195,7 +195,7 @@ describe('Signal detection rules, custom', () => { cy.get(DEFINITION_STEP) .eq(DEFINITION_TIMELINE) .invoke('text') - .should('eql', 'Default blank timeline'); + .should('eql', 'None'); cy.get(SCHEDULE_STEP) .eq(SCHEDULE_RUNS) diff --git a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts index ad56c905aa4fc5..e8b7441718765c 100644 --- a/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts +++ b/x-pack/plugins/siem/cypress/integration/signal_detection_rules_ml.spec.ts @@ -183,7 +183,7 @@ describe('Signal detection rules, machine learning', () => { cy.get(DEFINITION_STEP) .eq(DEFINITION_TIMELINE) .invoke('text') - .should('eql', 'Default blank timeline'); + .should('eql', 'None'); cy.get(SCHEDULE_STEP) .eq(SCHEDULE_RUNS) diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx index babfebb2c6ca7c..14afa63ef36097 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_define_rule/schema.tsx @@ -168,8 +168,7 @@ export const schema: FormSchema = { helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTimelineTemplateHelpText', { - defaultMessage: - 'Select an existing timeline to use as a template when investigating generated signals.', + defaultMessage: 'Select which timeline to use when investigating generated signals.', } ), }, diff --git a/x-pack/plugins/siem/public/timelines/components/open_timeline/index.test.tsx b/x-pack/plugins/siem/public/timelines/components/open_timeline/index.test.tsx index 07bfe81c216fe5..c1103346cb1aad 100644 --- a/x-pack/plugins/siem/public/timelines/components/open_timeline/index.test.tsx +++ b/x-pack/plugins/siem/public/timelines/components/open_timeline/index.test.tsx @@ -475,7 +475,10 @@ describe('StatefulOpenTimeline', () => { ).toEqual('elastic'); }); - test('it renders the tabs', async () => { + /** + * enable this test when createtTemplateTimeline is ready + */ + test.skip('it renders the tabs', async () => { const wrapper = mount( diff --git a/x-pack/plugins/siem/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/siem/public/timelines/components/open_timeline/index.tsx index 735ccdd19a5614..f9c9d28ad89e16 100644 --- a/x-pack/plugins/siem/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/siem/public/timelines/components/open_timeline/index.tsx @@ -52,6 +52,12 @@ interface OwnProps { onOpenTimeline?: (timeline: TimelineModel) => void; } +/** + * CreateTemplateTimelineBtn + * Remove the comment here to enable template timeline + */ +export const disableTemplate = true; + export type OpenTimelineOwnProps = OwnProps & Pick< OpenTimelineProps, @@ -275,7 +281,7 @@ export const StatefulOpenTimelineComponent = React.memo( selectedItems={selectedItems} sortDirection={sortDirection} sortField={sortField} - tabs={timelineTabs} + tabs={!disableTemplate ? timelineTabs : undefined} title={title} totalSearchResultsCount={totalCount} /> @@ -302,7 +308,7 @@ export const StatefulOpenTimelineComponent = React.memo( selectedItems={selectedItems} sortDirection={sortDirection} sortField={sortField} - tabs={timelineFilters} + tabs={!disableTemplate ? timelineFilters : undefined} title={title} totalSearchResultsCount={totalCount} /> diff --git a/x-pack/plugins/siem/public/timelines/components/open_timeline/open_timeline.tsx b/x-pack/plugins/siem/public/timelines/components/open_timeline/open_timeline.tsx index fec5295db8c12f..3de1518f215fb4 100644 --- a/x-pack/plugins/siem/public/timelines/components/open_timeline/open_timeline.tsx +++ b/x-pack/plugins/siem/public/timelines/components/open_timeline/open_timeline.tsx @@ -151,7 +151,7 @@ export const OpenTimeline = React.memo( /> - {tabs} + {!!tabs && tabs} = ({ getSelectableOptions={handleGetSelectableOptions} onClosePopover={handleClosePopover} onTimelineChange={onTimelineChange} + timelineType={TimelineType.default} /> ); diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/search_super_select/index.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/search_super_select/index.tsx index 337f0e34687ea0..b549fdab8ea4a4 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/search_super_select/index.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/search_super_select/index.tsx @@ -11,6 +11,7 @@ import { createGlobalStyle } from 'styled-components'; import { OpenTimelineResult } from '../../open_timeline/types'; import { SelectableTimeline } from '../selectable_timeline'; import * as i18n from '../translations'; +import { TimelineType, TimelineTypeLiteral } from '../../../../../common/types/timeline'; const SearchTimelineSuperSelectGlobalStyle = createGlobalStyle` .euiPopover__panel.euiPopover__panel-isOpen.timeline-search-super-select-popover__popoverPanel { @@ -24,6 +25,7 @@ interface SearchTimelineSuperSelectProps { hideUntitled?: boolean; timelineId: string | null; timelineTitle: string | null; + timelineType?: TimelineTypeLiteral; onTimelineChange: (timelineTitle: string, timelineId: string | null) => void; } @@ -50,6 +52,7 @@ const SearchTimelineSuperSelectComponent: React.FC { const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -121,6 +124,7 @@ const SearchTimelineSuperSelectComponent: React.FC diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/selectable_timeline/index.test.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/selectable_timeline/index.test.tsx new file mode 100644 index 00000000000000..2b67cf75dcff9e --- /dev/null +++ b/x-pack/plugins/siem/public/timelines/components/timeline/selectable_timeline/index.test.tsx @@ -0,0 +1,112 @@ +/* + * 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 React from 'react'; +import { shallow, ShallowWrapper, mount } from 'enzyme'; +import { TimelineType } from '../../../../../common/types/timeline'; +import { SortFieldTimeline, Direction } from '../../../../graphql/types'; +import { SearchProps } from './'; + +describe('SelectableTimeline', () => { + const mockFetchAllTimeline = jest.fn(); + const mockEuiSelectable = jest.fn(); + + jest.doMock('@elastic/eui', () => { + const originalModule = jest.requireActual('@elastic/eui'); + return { + ...originalModule, + EuiSelectable: mockEuiSelectable.mockImplementation(({ children }) =>
{children}
), + }; + }); + + jest.doMock('../../../containers/all', () => { + return { + useGetAllTimeline: jest.fn(() => ({ + fetchAllTimeline: mockFetchAllTimeline, + timelines: [], + })), + }; + }); + + const { + SelectableTimeline, + + ORIGINAL_PAGE_SIZE, + } = jest.requireActual('./'); + + const props = { + hideUntitled: false, + getSelectableOptions: jest.fn(), + onClosePopover: jest.fn(), + onTimelineChange: jest.fn(), + timelineType: TimelineType.default, + }; + + describe('should render', () => { + let wrapper: ShallowWrapper; + + describe('timeline', () => { + beforeAll(() => { + wrapper = shallow(); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + test('render placeholder', () => { + const searchProps: SearchProps = wrapper + .find('[data-test-subj="selectable-input"]') + .prop('searchProps'); + expect(searchProps.placeholder).toEqual('e.g. Timeline name or description'); + }); + }); + + describe('template timeline', () => { + const templateTimelineProps = { ...props, timelineType: TimelineType.template }; + beforeAll(() => { + wrapper = shallow(); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + test('render placeholder', () => { + const searchProps: SearchProps = wrapper + .find('[data-test-subj="selectable-input"]') + .prop('searchProps'); + expect(searchProps.placeholder).toEqual('e.g. Template timeline name or description'); + }); + }); + }); + + describe('fetchAllTimeline', () => { + const args = { + pageInfo: { + pageIndex: 1, + pageSize: ORIGINAL_PAGE_SIZE, + }, + search: '', + sort: { + sortField: SortFieldTimeline.updated, + sortOrder: Direction.desc, + }, + onlyUserFavorite: false, + timelineType: TimelineType.default, + }; + beforeAll(() => { + mount(); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + test('shoule be called with correct args', () => { + expect(mockFetchAllTimeline).toBeCalledWith(args); + }); + }); +}); diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/selectable_timeline/index.tsx b/x-pack/plugins/siem/public/timelines/components/timeline/selectable_timeline/index.tsx index fb3cb3f177ca04..b10ecc31acef84 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/selectable_timeline/index.tsx +++ b/x-pack/plugins/siem/public/timelines/components/timeline/selectable_timeline/index.tsx @@ -21,7 +21,10 @@ import React, { memo, useCallback, useMemo, useState, useEffect } from 'react'; import { ListProps } from 'react-virtualized'; import styled from 'styled-components'; -import { TimelineType, TimelineTypeLiteralWithNull } from '../../../../../common/types/timeline'; +import { + TimelineTypeLiteralWithNull, + TimelineTypeLiteral, +} from '../../../../../common/types/timeline'; import { useGetAllTimeline } from '../../../containers/all'; import { SortFieldTimeline, Direction } from '../../../../graphql/types'; @@ -29,7 +32,6 @@ import { isUntitled } from '../../open_timeline/helpers'; import * as i18nTimeline from '../../open_timeline/translations'; import { OpenTimelineResult } from '../../open_timeline/types'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; - import * as i18n from '../translations'; const MyEuiFlexItem = styled(EuiFlexItem)` @@ -66,7 +68,7 @@ const EuiSelectableContainer = styled.div<{ isLoading: boolean }>` } `; -const ORIGINAL_PAGE_SIZE = 50; +export const ORIGINAL_PAGE_SIZE = 50; const POPOVER_HEIGHT = 260; const TIMELINE_ITEM_HEIGHT = 50; @@ -77,7 +79,7 @@ export interface GetSelectableOptions { searchTimelineValue: string; } -interface SelectableTimelineProps { +export interface SelectableTimelineProps { hideUntitled?: boolean; getSelectableOptions: ({ timelines, @@ -87,6 +89,16 @@ interface SelectableTimelineProps { }: GetSelectableOptions) => EuiSelectableOption[]; onClosePopover: () => void; onTimelineChange: (timelineTitle: string, timelineId: string | null) => void; + timelineType: TimelineTypeLiteral; +} + +export interface SearchProps { + 'data-test-subj'?: string; + isLoading: boolean; + placeholder: string; + onSearch: (arg: string) => void; + incremental: boolean; + inputRef: (arg: HTMLElement) => void; } const SelectableTimelineComponent: React.FC = ({ @@ -94,10 +106,11 @@ const SelectableTimelineComponent: React.FC = ({ getSelectableOptions, onClosePopover, onTimelineChange, + timelineType, }) => { const [pageSize, setPageSize] = useState(ORIGINAL_PAGE_SIZE); const [heightTrigger, setHeightTrigger] = useState(0); - const [searchTimelineValue, setSearchTimelineValue] = useState(''); + const [searchTimelineValue, setSearchTimelineValue] = useState(''); const [onlyFavorites, setOnlyFavorites] = useState(false); const [searchRef, setSearchRef] = useState(null); const { fetchAllTimeline, timelines, loading, totalCount: timelineCount } = useGetAllTimeline(); @@ -220,6 +233,17 @@ const SelectableTimelineComponent: React.FC = ({ [searchRef, onlyFavorites, handleOnToggleOnlyFavorites] ); + const searchProps: SearchProps = { + 'data-test-subj': 'timeline-super-select-search-box', + isLoading: loading, + placeholder: useMemo(() => i18n.SEARCH_BOX_TIMELINE_PLACEHOLDER(timelineType), [timelineType]), + onSearch: onSearchTimeline, + incremental: false, + inputRef: (ref: HTMLElement) => { + setSearchRef(ref); + }, + }; + useEffect(() => { fetchAllTimeline({ pageInfo: { @@ -232,13 +256,14 @@ const SelectableTimelineComponent: React.FC = ({ sortOrder: Direction.desc, }, onlyUserFavorite: onlyFavorites, - timelineType: TimelineType.default, + timelineType, }); - }, [onlyFavorites, pageSize, searchTimelineValue]); + }, [onlyFavorites, pageSize, searchTimelineValue, timelineType]); return ( = ({ renderOption={renderTimelineOption} onChange={handleTimelineChange} searchable - searchProps={{ - 'data-test-subj': 'timeline-super-select-search-box', - isLoading: loading, - placeholder: i18n.SEARCH_BOX_TIMELINE_PLACEHOLDER, - onSearch: onSearchTimeline, - incremental: false, - inputRef: (ref: HTMLElement) => { - setSearchRef(ref); - }, - }} + searchProps={searchProps} singleSelection={true} options={getSelectableOptions({ timelines, onlyFavorites, searchTimelineValue, - timelineType: TimelineType.default, + timelineType, })} > {(list, search) => ( diff --git a/x-pack/plugins/siem/public/timelines/components/timeline/translations.ts b/x-pack/plugins/siem/public/timelines/components/timeline/translations.ts index 101837168350f8..a7d656b69c5391 100644 --- a/x-pack/plugins/siem/public/timelines/components/timeline/translations.ts +++ b/x-pack/plugins/siem/public/timelines/components/timeline/translations.ts @@ -5,9 +5,10 @@ */ import { i18n } from '@kbn/i18n'; +import { TimelineTypeLiteral, TimelineType } from '../../../../common/types/timeline'; export const DEFAULT_TIMELINE_TITLE = i18n.translate('xpack.siem.timeline.defaultTimelineTitle', { - defaultMessage: 'Default blank timeline', + defaultMessage: 'None', }); export const DEFAULT_TIMELINE_DESCRIPTION = i18n.translate( @@ -17,12 +18,11 @@ export const DEFAULT_TIMELINE_DESCRIPTION = i18n.translate( } ); -export const SEARCH_BOX_TIMELINE_PLACEHOLDER = i18n.translate( - 'xpack.siem.timeline.searchBoxPlaceholder', - { - defaultMessage: 'e.g. timeline name or description', - } -); +export const SEARCH_BOX_TIMELINE_PLACEHOLDER = (timelineType: TimelineTypeLiteral) => + i18n.translate('xpack.siem.timeline.searchBoxPlaceholder', { + values: { timeline: timelineType === TimelineType.template ? 'Template timeline' : 'Timeline' }, + defaultMessage: 'e.g. {timeline} name or description', + }); export const INSERT_TIMELINE = i18n.translate('xpack.siem.insert.timeline.insertTimelineButton', { defaultMessage: 'Insert timeline link', diff --git a/x-pack/plugins/siem/server/lib/timeline/saved_object.ts b/x-pack/plugins/siem/server/lib/timeline/saved_object.ts index 1a2a2a9c20a67a..2e48d0db0ba768 100644 --- a/x-pack/plugins/siem/server/lib/timeline/saved_object.ts +++ b/x-pack/plugins/siem/server/lib/timeline/saved_object.ts @@ -142,7 +142,12 @@ export const getAllTimeline = async ( searchFields: onlyUserFavorite ? ['title', 'description', 'favorite.keySearch'] : ['title', 'description'], - filter: getTimelineTypeFilter(timelineType, false), + /** + * CreateTemplateTimelineBtn + * Remove the comment here to enable template timeline and apply the change below + * filter: getTimelineTypeFilter(timelineType, false) + */ + filter: getTimelineTypeFilter(TimelineType.default, false), sortField: sort != null ? sort.sortField : undefined, sortOrder: sort != null ? sort.sortOrder : undefined, }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 95ef90cab04de1..94b5ce719ccaa9 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -14255,7 +14255,6 @@ "xpack.siem.timeline.callOut.unauthorized.message.description": "SIEM アプリケーションでタイムラインを自動保存するにはパーミッションが必要ですが、引き続きタイムラインを使用してセキュリティイベントの検索とフィルタリングを行うことはできます。", "xpack.siem.timeline.categoryTooltip": "カテゴリー", "xpack.siem.timeline.defaultTimelineDescription": "新しいタイムラインを作成するときにデフォルトで提供されるタイムライン。", - "xpack.siem.timeline.defaultTimelineTitle": "デフォルトの空白タイムライン", "xpack.siem.timeline.descriptionTooltip": "説明", "xpack.siem.timeline.destination": "送信先", "xpack.siem.timeline.eventsSelect.actions.pinSelected": "選択項目にピン付け", @@ -14297,7 +14296,6 @@ "xpack.siem.timeline.rangePicker.oneMonth": "1 か月", "xpack.siem.timeline.rangePicker.oneWeek": "1 週間", "xpack.siem.timeline.rangePicker.oneYear": "1 年", - "xpack.siem.timeline.searchBoxPlaceholder": "例:タイムライン名、または説明", "xpack.siem.timeline.searchOrFilter.eventTypeAllEvent": "すべてのイベント", "xpack.siem.timeline.searchOrFilter.eventTypeRawEvent": "未加工イベント", "xpack.siem.timeline.searchOrFilter.eventTypeSignalEvent": "シグナルイベント", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1d1ce38f6a91cf..06701fa2f02136 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -14304,7 +14304,6 @@ "xpack.siem.timeline.rangePicker.oneMonth": "1 个月", "xpack.siem.timeline.rangePicker.oneWeek": "1 周", "xpack.siem.timeline.rangePicker.oneYear": "1 年", - "xpack.siem.timeline.searchBoxPlaceholder": "例如时间线名称或描述", "xpack.siem.timeline.searchOrFilter.eventTypeAllEvent": "所有事件", "xpack.siem.timeline.searchOrFilter.eventTypeRawEvent": "原始事件", "xpack.siem.timeline.searchOrFilter.eventTypeSignalEvent": "信号事件",