Skip to content

Commit f84e8b5

Browse files
angoraycXavierMelasticmachine
authored
[SIEM] Create template timeline (#63136) (#64750)
* init routes for template timeline * create template timeline * add create/update timelines route * update api entry point * fix types * add template type * fix types * add types and template timeline id * fix types * update import timeline to handle template timeline * unit test * sudo code * remove class for savedobject * add template timeline version * clean up arguments * fix types for framework request * show filter in find * fix create template timeline * update mock data * handle missing timeline when exporting * update the order for timeline routes * update schemas * move type to common folder so we can re-use them on UI and server side * fix types + integrate persist with epic timeline * update all timeline when persit timeline * add timeline api readme * fix validation error * fix unit test * display error if unexpected format is given * fix issue with reftech all timeline query * fix flashing timeline while refetch * fix types * fix types * fix dependency * fix timeline deletion * remove redundant dependency * add i18n message * fix unit test Co-authored-by: Xavier Mouligneau <[email protected]> Co-authored-by: Elastic Machine <[email protected]> Co-authored-by: Xavier Mouligneau <[email protected]> Co-authored-by: Elastic Machine <[email protected]>
1 parent 7985bda commit f84e8b5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3040
-1100
lines changed

x-pack/plugins/siem/server/lib/timeline/types.ts renamed to x-pack/plugins/siem/common/types/timeline/index.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@
77
/* eslint-disable @typescript-eslint/no-empty-interface */
88

99
import * as runtimeTypes from 'io-ts';
10+
import { SavedObjectsClient } from 'kibana/server';
1011

11-
import { unionWithNullType } from '../framework';
12-
import { NoteSavedObjectToReturnRuntimeType, NoteSavedObject } from '../note/types';
13-
import {
14-
PinnedEventToReturnSavedObjectRuntimeType,
15-
PinnedEventSavedObject,
16-
} from '../pinned_event/types';
17-
import { SavedObjectsClient } from '../../../../../../src/core/server';
12+
import { unionWithNullType } from '../../utility_types';
13+
import { NoteSavedObject, NoteSavedObjectToReturnRuntimeType } from './note';
14+
import { PinnedEventToReturnSavedObjectRuntimeType, PinnedEventSavedObject } from './pinned_event';
1815

1916
/*
2017
* ColumnHeader Types
@@ -136,6 +133,17 @@ const SavedSortRuntimeType = runtimeTypes.partial({
136133
/*
137134
* Timeline Types
138135
*/
136+
137+
export enum TimelineType {
138+
default = 'default',
139+
template = 'template',
140+
}
141+
142+
export const TimelineTypeLiteralRt = runtimeTypes.union([
143+
runtimeTypes.literal(TimelineType.template),
144+
runtimeTypes.literal(TimelineType.default),
145+
]);
146+
139147
export const SavedTimelineRuntimeType = runtimeTypes.partial({
140148
columns: unionWithNullType(runtimeTypes.array(SavedColumnHeaderRuntimeType)),
141149
dataProviders: unionWithNullType(runtimeTypes.array(SavedDataProviderRuntimeType)),
@@ -146,6 +154,9 @@ export const SavedTimelineRuntimeType = runtimeTypes.partial({
146154
kqlMode: unionWithNullType(runtimeTypes.string),
147155
kqlQuery: unionWithNullType(SavedFilterQueryQueryRuntimeType),
148156
title: unionWithNullType(runtimeTypes.string),
157+
templateTimelineId: unionWithNullType(runtimeTypes.string),
158+
templateTimelineVersion: unionWithNullType(runtimeTypes.number),
159+
timelineType: unionWithNullType(TimelineTypeLiteralRt),
149160
dateRange: unionWithNullType(SavedDateRangePickerRuntimeType),
150161
savedQueryId: unionWithNullType(runtimeTypes.string),
151162
sort: unionWithNullType(SavedSortRuntimeType),
@@ -192,6 +203,25 @@ export const TimelineSavedToReturnObjectRuntimeType = runtimeTypes.intersection(
192203
export interface TimelineSavedObject
193204
extends runtimeTypes.TypeOf<typeof TimelineSavedToReturnObjectRuntimeType> {}
194205

206+
/**
207+
* All Timeline Saved object type with metadata
208+
*/
209+
export const TimelineResponseType = runtimeTypes.type({
210+
data: runtimeTypes.type({
211+
persistTimeline: runtimeTypes.intersection([
212+
runtimeTypes.partial({
213+
code: unionWithNullType(runtimeTypes.number),
214+
message: unionWithNullType(runtimeTypes.string),
215+
}),
216+
runtimeTypes.type({
217+
timeline: TimelineSavedToReturnObjectRuntimeType,
218+
}),
219+
]),
220+
}),
221+
});
222+
223+
export interface TimelineResponse extends runtimeTypes.TypeOf<typeof TimelineResponseType> {}
224+
195225
/**
196226
* All Timeline Saved object type with metadata
197227
*/
@@ -234,6 +264,11 @@ export type ExportedTimelines = TimelineSavedObject &
234264
pinnedEventIds: string[];
235265
};
236266

267+
export interface ExportTimelineNotFoundError {
268+
statusCode: number;
269+
message: string;
270+
}
271+
237272
export interface BulkGetInput {
238273
type: string;
239274
id: string;

x-pack/plugins/siem/server/lib/note/types.ts renamed to x-pack/plugins/siem/common/types/timeline/note/index.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import * as runtimeTypes from 'io-ts';
1010

11-
import { unionWithNullType } from '../framework';
11+
import { unionWithNullType } from '../../../utility_types';
1212

1313
/*
1414
* Note Types
@@ -56,11 +56,7 @@ export const NoteSavedObjectToReturnRuntimeType = runtimeTypes.intersection([
5656
version: runtimeTypes.string,
5757
}),
5858
runtimeTypes.partial({
59-
timelineVersion: runtimeTypes.union([
60-
runtimeTypes.string,
61-
runtimeTypes.null,
62-
runtimeTypes.undefined,
63-
]),
59+
timelineVersion: unionWithNullType(runtimeTypes.string),
6460
}),
6561
]);
6662

x-pack/plugins/siem/server/lib/pinned_event/types.ts renamed to x-pack/plugins/siem/common/types/timeline/pinned_event/index.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import * as runtimeTypes from 'io-ts';
1010

11-
import { unionWithNullType } from '../framework';
11+
import { unionWithNullType } from '../../../utility_types';
1212

1313
/*
1414
* Note Types
@@ -40,11 +40,7 @@ export const PinnedEventSavedObjectRuntimeType = runtimeTypes.intersection([
4040
}),
4141
runtimeTypes.partial({
4242
pinnedEventId: unionWithNullType(runtimeTypes.string),
43-
timelineVersion: runtimeTypes.union([
44-
runtimeTypes.string,
45-
runtimeTypes.null,
46-
runtimeTypes.undefined,
47-
]),
43+
timelineVersion: unionWithNullType(runtimeTypes.string),
4844
}),
4945
]);
5046

@@ -55,11 +51,7 @@ export const PinnedEventToReturnSavedObjectRuntimeType = runtimeTypes.intersecti
5551
}),
5652
SavedPinnedEventRuntimeType,
5753
runtimeTypes.partial({
58-
timelineVersion: runtimeTypes.union([
59-
runtimeTypes.string,
60-
runtimeTypes.null,
61-
runtimeTypes.undefined,
62-
]),
54+
timelineVersion: unionWithNullType(runtimeTypes.string),
6355
}),
6456
]);
6557

x-pack/plugins/siem/common/utility_types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7+
import * as runtimeTypes from 'io-ts';
78
import { ReactNode } from 'react';
89

910
// This type is for typing EuiDescriptionList
1011
export interface DescriptionList {
1112
title: NonNullable<ReactNode>;
1213
description: NonNullable<ReactNode>;
1314
}
15+
16+
export const unionWithNullType = <T extends runtimeTypes.Mixed>(type: T) =>
17+
runtimeTypes.union([type, runtimeTypes.null]);

x-pack/plugins/siem/public/components/matrix_histogram/__snapshots__/index.test.tsx.snap

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugins/siem/public/components/open_timeline/export_timeline/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { DeleteTimelines } from '../types';
99

1010
import { TimelineDownloader } from './export_timeline';
1111
import { DeleteTimelineModalOverlay } from '../delete_timeline_modal';
12-
import { exportSelectedTimeline } from '../../../containers/timeline/all/api';
12+
import { exportSelectedTimeline } from '../../../containers/timeline/api';
1313

1414
export interface ExportTimeline {
1515
disableExportTimelineDownloader: () => void;

x-pack/plugins/siem/public/components/open_timeline/index.test.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,34 @@ import { TestProviderWithoutDragAndDrop, apolloClient } from '../../mock/test_pr
1515
import { mockOpenTimelineQueryResults } from '../../mock/timeline_results';
1616
import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../pages/timelines/timelines_page';
1717

18-
import { StatefulOpenTimeline } from '.';
1918
import { NotePreviews } from './note_previews';
2019
import { OPEN_TIMELINE_CLASS_NAME } from './helpers';
21-
20+
import { StatefulOpenTimeline } from '.';
21+
import { useGetAllTimeline, getAllTimeline } from '../../containers/timeline/all';
2222
jest.mock('../../lib/kibana');
23+
jest.mock('../../containers/timeline/all', () => {
24+
const originalModule = jest.requireActual('../../containers/timeline/all');
25+
return {
26+
useGetAllTimeline: jest.fn(),
27+
getAllTimeline: originalModule.getAllTimeline,
28+
};
29+
});
2330

2431
describe('StatefulOpenTimeline', () => {
2532
const theme = () => ({ eui: euiDarkVars, darkMode: true });
2633
const title = 'All Timelines / Open Timelines';
34+
beforeEach(() => {
35+
((useGetAllTimeline as unknown) as jest.Mock).mockReturnValue({
36+
fetchAllTimeline: jest.fn(),
37+
timelines: getAllTimeline(
38+
'',
39+
mockOpenTimelineQueryResults[0].result.data?.getAllTimeline?.timeline ?? []
40+
),
41+
loading: false,
42+
totalCount: mockOpenTimelineQueryResults[0].result.data.getAllTimeline.totalCount,
43+
refetch: jest.fn(),
44+
});
45+
});
2746

2847
test('it has the expected initial state', () => {
2948
const wrapper = mount(
@@ -459,6 +478,8 @@ describe('StatefulOpenTimeline', () => {
459478
.find('[data-test-subj="expand-notes"]')
460479
.first()
461480
.simulate('click');
481+
expect(wrapper.find('[data-test-subj="note-previews-container"]').exists()).toEqual(true);
482+
expect(wrapper.find('[data-test-subj="updated-by"]').exists()).toEqual(true);
462483

463484
expect(
464485
wrapper
@@ -532,7 +553,7 @@ describe('StatefulOpenTimeline', () => {
532553
test('it renders the expected count of matching timelines when no query has been entered', async () => {
533554
const wrapper = mount(
534555
<ThemeProvider theme={theme}>
535-
<MockedProvider mocks={mockOpenTimelineQueryResults} addTypename={false}>
556+
<MockedProvider addTypename={false}>
536557
<TestProviderWithoutDragAndDrop>
537558
<StatefulOpenTimeline
538559
data-test-subj="stateful-timeline"

0 commit comments

Comments
 (0)