Skip to content

Commit

Permalink
[SIEM] Create template timeline (#63136)
Browse files Browse the repository at this point in the history
* 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 <189600+XavierM@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
3 people authored Apr 29, 2020
1 parent ad89b1e commit a42c202
Show file tree
Hide file tree
Showing 56 changed files with 3,040 additions and 1,100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@
/* eslint-disable @typescript-eslint/no-empty-interface */

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

import { unionWithNullType } from '../framework';
import { NoteSavedObjectToReturnRuntimeType, NoteSavedObject } from '../note/types';
import {
PinnedEventToReturnSavedObjectRuntimeType,
PinnedEventSavedObject,
} from '../pinned_event/types';
import { SavedObjectsClient } from '../../../../../../src/core/server';
import { unionWithNullType } from '../../utility_types';
import { NoteSavedObject, NoteSavedObjectToReturnRuntimeType } from './note';
import { PinnedEventToReturnSavedObjectRuntimeType, PinnedEventSavedObject } from './pinned_event';

/*
* ColumnHeader Types
Expand Down Expand Up @@ -136,6 +133,17 @@ const SavedSortRuntimeType = runtimeTypes.partial({
/*
* Timeline Types
*/

export enum TimelineType {
default = 'default',
template = 'template',
}

export const TimelineTypeLiteralRt = runtimeTypes.union([
runtimeTypes.literal(TimelineType.template),
runtimeTypes.literal(TimelineType.default),
]);

export const SavedTimelineRuntimeType = runtimeTypes.partial({
columns: unionWithNullType(runtimeTypes.array(SavedColumnHeaderRuntimeType)),
dataProviders: unionWithNullType(runtimeTypes.array(SavedDataProviderRuntimeType)),
Expand All @@ -146,6 +154,9 @@ export const SavedTimelineRuntimeType = runtimeTypes.partial({
kqlMode: unionWithNullType(runtimeTypes.string),
kqlQuery: unionWithNullType(SavedFilterQueryQueryRuntimeType),
title: unionWithNullType(runtimeTypes.string),
templateTimelineId: unionWithNullType(runtimeTypes.string),
templateTimelineVersion: unionWithNullType(runtimeTypes.number),
timelineType: unionWithNullType(TimelineTypeLiteralRt),
dateRange: unionWithNullType(SavedDateRangePickerRuntimeType),
savedQueryId: unionWithNullType(runtimeTypes.string),
sort: unionWithNullType(SavedSortRuntimeType),
Expand Down Expand Up @@ -192,6 +203,25 @@ export const TimelineSavedToReturnObjectRuntimeType = runtimeTypes.intersection(
export interface TimelineSavedObject
extends runtimeTypes.TypeOf<typeof TimelineSavedToReturnObjectRuntimeType> {}

/**
* All Timeline Saved object type with metadata
*/
export const TimelineResponseType = runtimeTypes.type({
data: runtimeTypes.type({
persistTimeline: runtimeTypes.intersection([
runtimeTypes.partial({
code: unionWithNullType(runtimeTypes.number),
message: unionWithNullType(runtimeTypes.string),
}),
runtimeTypes.type({
timeline: TimelineSavedToReturnObjectRuntimeType,
}),
]),
}),
});

export interface TimelineResponse extends runtimeTypes.TypeOf<typeof TimelineResponseType> {}

/**
* All Timeline Saved object type with metadata
*/
Expand Down Expand Up @@ -234,6 +264,11 @@ export type ExportedTimelines = TimelineSavedObject &
pinnedEventIds: string[];
};

export interface ExportTimelineNotFoundError {
statusCode: number;
message: string;
}

export interface BulkGetInput {
type: string;
id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import * as runtimeTypes from 'io-ts';

import { unionWithNullType } from '../framework';
import { unionWithNullType } from '../../../utility_types';

/*
* Note Types
Expand Down Expand Up @@ -56,11 +56,7 @@ export const NoteSavedObjectToReturnRuntimeType = runtimeTypes.intersection([
version: runtimeTypes.string,
}),
runtimeTypes.partial({
timelineVersion: runtimeTypes.union([
runtimeTypes.string,
runtimeTypes.null,
runtimeTypes.undefined,
]),
timelineVersion: unionWithNullType(runtimeTypes.string),
}),
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import * as runtimeTypes from 'io-ts';

import { unionWithNullType } from '../framework';
import { unionWithNullType } from '../../../utility_types';

/*
* Note Types
Expand Down Expand Up @@ -40,11 +40,7 @@ export const PinnedEventSavedObjectRuntimeType = runtimeTypes.intersection([
}),
runtimeTypes.partial({
pinnedEventId: unionWithNullType(runtimeTypes.string),
timelineVersion: runtimeTypes.union([
runtimeTypes.string,
runtimeTypes.null,
runtimeTypes.undefined,
]),
timelineVersion: unionWithNullType(runtimeTypes.string),
}),
]);

Expand All @@ -55,11 +51,7 @@ export const PinnedEventToReturnSavedObjectRuntimeType = runtimeTypes.intersecti
}),
SavedPinnedEventRuntimeType,
runtimeTypes.partial({
timelineVersion: runtimeTypes.union([
runtimeTypes.string,
runtimeTypes.null,
runtimeTypes.undefined,
]),
timelineVersion: unionWithNullType(runtimeTypes.string),
}),
]);

Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/siem/common/utility_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/

import * as runtimeTypes from 'io-ts';
import { ReactNode } from 'react';

// This type is for typing EuiDescriptionList
export interface DescriptionList {
title: NonNullable<ReactNode>;
description: NonNullable<ReactNode>;
}

export const unionWithNullType = <T extends runtimeTypes.Mixed>(type: T) =>
runtimeTypes.union([type, runtimeTypes.null]);

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { DeleteTimelines } from '../types';

import { TimelineDownloader } from './export_timeline';
import { DeleteTimelineModalOverlay } from '../delete_timeline_modal';
import { exportSelectedTimeline } from '../../../containers/timeline/all/api';
import { exportSelectedTimeline } from '../../../containers/timeline/api';

export interface ExportTimeline {
disableExportTimelineDownloader: () => void;
Expand Down
27 changes: 24 additions & 3 deletions x-pack/plugins/siem/public/components/open_timeline/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,34 @@ import { TestProviderWithoutDragAndDrop, apolloClient } from '../../mock/test_pr
import { mockOpenTimelineQueryResults } from '../../mock/timeline_results';
import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../pages/timelines/timelines_page';

import { StatefulOpenTimeline } from '.';
import { NotePreviews } from './note_previews';
import { OPEN_TIMELINE_CLASS_NAME } from './helpers';

import { StatefulOpenTimeline } from '.';
import { useGetAllTimeline, getAllTimeline } from '../../containers/timeline/all';
jest.mock('../../lib/kibana');
jest.mock('../../containers/timeline/all', () => {
const originalModule = jest.requireActual('../../containers/timeline/all');
return {
useGetAllTimeline: jest.fn(),
getAllTimeline: originalModule.getAllTimeline,
};
});

describe('StatefulOpenTimeline', () => {
const theme = () => ({ eui: euiDarkVars, darkMode: true });
const title = 'All Timelines / Open Timelines';
beforeEach(() => {
((useGetAllTimeline as unknown) as jest.Mock).mockReturnValue({
fetchAllTimeline: jest.fn(),
timelines: getAllTimeline(
'',
mockOpenTimelineQueryResults[0].result.data?.getAllTimeline?.timeline ?? []
),
loading: false,
totalCount: mockOpenTimelineQueryResults[0].result.data.getAllTimeline.totalCount,
refetch: jest.fn(),
});
});

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

expect(
wrapper
Expand Down Expand Up @@ -532,7 +553,7 @@ describe('StatefulOpenTimeline', () => {
test('it renders the expected count of matching timelines when no query has been entered', async () => {
const wrapper = mount(
<ThemeProvider theme={theme}>
<MockedProvider mocks={mockOpenTimelineQueryResults} addTypename={false}>
<MockedProvider addTypename={false}>
<TestProviderWithoutDragAndDrop>
<StatefulOpenTimeline
data-test-subj="stateful-timeline"
Expand Down
Loading

0 comments on commit a42c202

Please sign in to comment.