diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/api.ts b/x-pack/plugins/security_solution/common/api/timeline/model/api.ts index 5237772ef5e1c..a48d0889140a1 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/api.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/api.ts @@ -25,7 +25,7 @@ import { ErrorSchema } from './error_schema'; export const BareNoteSchema = runtimeTypes.intersection([ runtimeTypes.type({ - timelineId: unionWithNullType(runtimeTypes.string), + timelineId: runtimeTypes.string, }), runtimeTypes.partial({ eventId: unionWithNullType(runtimeTypes.string), @@ -51,9 +51,6 @@ export const NoteRuntimeType = runtimeTypes.intersection([ noteId: runtimeTypes.string, version: runtimeTypes.string, }), - runtimeTypes.partial({ - timelineVersion: unionWithNullType(runtimeTypes.string), - }), ]); export type Note = runtimeTypes.TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/components.yaml b/x-pack/plugins/security_solution/common/api/timeline/model/components.yaml index 3ecd8d7f153ed..9c007aa195838 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/components.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/model/components.yaml @@ -18,7 +18,7 @@ components: type: object properties: columns: - $ref: '#/components/schemas/ColumnHeaderResult' + $ref: '#/components/schemas/ColumnHeaderResult' created: type: number createdBy: @@ -244,9 +244,6 @@ components: type: string version: type: string - timelineVersion: - nullable: true - type: string GlobalNote: type: object properties: @@ -254,9 +251,6 @@ components: type: string version: type: string - timelineVersion: - nullable: true - type: string note: type: string timelineId: @@ -278,8 +272,6 @@ components: type: string version: type: string - timelineVersion: - type: string RowRendererId: type: string enum: @@ -384,8 +376,6 @@ components: type: string version: type: string - timelineVersion: - type: string Sort: type: object properties: @@ -415,30 +405,30 @@ components: description: The status of the timeline. Valid values are `active`, `draft`, and `immutable`. ImportTimelines: allOf: - - $ref: '#/components/schemas/SavedTimeline' - - type: object - properties: - savedObjectId: - type: string - nullable: true - version: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + savedObjectId: + type: string + nullable: true + version: + type: string + nullable: true + globalNotes: + nullable: true + type: array + items: + $ref: '#/components/schemas/BareNote' + eventNotes: + nullable: true + type: array + items: + $ref: '#/components/schemas/BareNote' + pinnedEventIds: + nullable: true + type: array + items: type: string - nullable: true - globalNotes: - nullable: true - type: array - items: - $ref: '#/components/schemas/BareNote' - eventNotes: - nullable: true - type: array - items: - $ref: '#/components/schemas/BareNote' - pinnedEventIds: - nullable: true - type: array - items: - type: string ImportTimelineResult: type: object properties: diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route_schema.yaml index 789d4e97e63f7..f8ba6ecc9747c 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route_schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route_schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Notes API - version: 8.9.0 + version: 8.14.0 externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-update.html description: Documentation diff --git a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.ts b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.ts index f446e4e065b35..369cc6cc57089 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.ts @@ -14,10 +14,10 @@ export const pinnedEventIds = unionWithNullType(runtimeTypes.array(runtimeTypes. export const persistPinnedEventSchema = runtimeTypes.intersection([ runtimeTypes.type({ eventId: runtimeTypes.string, + timelineId: runtimeTypes.string, }), runtimeTypes.partial({ pinnedEventId: unionWithNullType(runtimeTypes.string), - timelineId: unionWithNullType(runtimeTypes.string), }), ]); @@ -51,9 +51,6 @@ export const PinnedEventRuntimeType = runtimeTypes.intersection([ version: runtimeTypes.string, }), BarePinnedEventType, - runtimeTypes.partial({ - timelineVersion: unionWithNullType(runtimeTypes.string), - }), ]); export interface PinnedEvent extends runtimeTypes.TypeOf {} diff --git a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route_schema.yaml index 0d8611bfcab5f..6c39b80a782c6 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route_schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route_schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Pinned Event API (https://www.elastic.co/guide/en/security/current/_pin_an_event_to_an_existing_timeline.html) - version: 8.9.0 + version: 8.14.0 servers: - url: 'http://{kibana_host}:{port}' variables: @@ -33,7 +33,6 @@ paths: nullable: true timelineId: type: string - nullable: true responses: 200: description: Indicate the event was successfully pinned in the timeline. @@ -55,4 +54,4 @@ paths: message: type: string required: - - data \ No newline at end of file + - data diff --git a/x-pack/plugins/security_solution/common/types/timeline/note/saved_object.ts b/x-pack/plugins/security_solution/common/types/timeline/note/saved_object.ts index cc08077632631..1fad511424eda 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/note/saved_object.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/note/saved_object.ts @@ -16,7 +16,7 @@ import { unionWithNullType } from '../../../utility_types'; */ const SavedNoteRuntimeType = runtimeTypes.intersection([ runtimeTypes.type({ - timelineId: unionWithNullType(runtimeTypes.string), + timelineId: runtimeTypes.string, }), runtimeTypes.partial({ eventId: unionWithNullType(runtimeTypes.string), @@ -39,11 +39,6 @@ export const SavedObjectNoteRuntimeType = runtimeTypes.intersection([ }), runtimeTypes.partial({ noteId: runtimeTypes.string, - timelineVersion: runtimeTypes.union([ - runtimeTypes.string, - runtimeTypes.null, - runtimeTypes.undefined, - ]), }), ]); diff --git a/x-pack/plugins/security_solution/common/types/timeline/pinned_event/saved_object.ts b/x-pack/plugins/security_solution/common/types/timeline/pinned_event/saved_object.ts index 14c6fdfa72a8c..ec645c29a535d 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/pinned_event/saved_object.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/pinned_event/saved_object.ts @@ -38,7 +38,6 @@ export const SavedObjectPinnedEventRuntimeType = runtimeTypes.intersection([ }), runtimeTypes.partial({ pinnedEventId: unionWithNullType(runtimeTypes.string), - timelineVersion: unionWithNullType(runtimeTypes.string), }), ]); diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index 757eb40f1fcb0..7c2392445099c 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -41,7 +41,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -53,7 +52,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -65,7 +63,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -76,7 +73,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -88,7 +84,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -100,7 +95,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -112,7 +106,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -124,7 +117,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -163,7 +155,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -175,7 +166,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -187,7 +177,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -198,7 +187,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -210,7 +198,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -222,7 +209,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -234,7 +220,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -246,7 +231,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -285,7 +269,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -297,7 +280,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -309,7 +291,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -320,7 +301,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -332,7 +312,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -344,7 +323,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -356,7 +334,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -368,7 +345,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -407,7 +383,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -419,7 +394,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -431,7 +405,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -442,7 +415,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -454,7 +426,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -466,7 +437,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -478,7 +448,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -490,7 +459,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -529,7 +497,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -541,7 +508,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -553,7 +519,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -564,7 +529,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -576,7 +540,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -588,7 +551,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -600,7 +562,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -612,7 +573,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -651,7 +611,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -663,7 +622,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -675,7 +633,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -686,7 +643,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -698,7 +654,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -710,7 +665,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -722,7 +676,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -734,7 +687,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -773,7 +725,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -785,7 +736,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -797,7 +747,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -808,7 +757,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -820,7 +768,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -832,7 +779,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -844,7 +790,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -856,7 +801,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -895,7 +839,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -907,7 +850,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -919,7 +861,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -930,7 +871,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -942,7 +882,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -954,7 +893,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -966,7 +904,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -978,7 +915,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -1017,7 +953,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -1029,7 +964,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -1041,7 +975,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -1052,7 +985,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -1064,7 +996,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -1076,7 +1007,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -1088,7 +1018,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -1100,7 +1029,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -1139,7 +1067,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -1151,7 +1078,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -1163,7 +1089,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -1174,7 +1099,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -1186,7 +1110,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -1198,7 +1121,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -1210,7 +1132,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -1222,7 +1143,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -1261,7 +1181,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -1273,7 +1192,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -1285,7 +1203,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -1296,7 +1213,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -1308,7 +1224,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -1320,7 +1235,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -1332,7 +1246,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -1344,7 +1257,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -1383,7 +1295,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -1395,7 +1306,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -1407,7 +1317,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -1418,7 +1327,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -1430,7 +1338,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -1442,7 +1349,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -1454,7 +1360,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -1466,7 +1371,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', @@ -1505,7 +1409,6 @@ export const mockOpenTimelineQueryResults = { noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', - timelineVersion: null, updated: 1558404484133, updatedBy: 'elastic', version: 'WzEzOSwxXQ==', @@ -1517,7 +1420,6 @@ export const mockOpenTimelineQueryResults = { noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', - timelineVersion: null, updated: 1558404474317, updatedBy: 'elastic', version: 'WzEzNywxXQ==', @@ -1529,7 +1431,6 @@ export const mockOpenTimelineQueryResults = { noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', - timelineVersion: null, updated: 1558404491600, updatedBy: 'elastic', version: 'WzE0MSwxXQ==', @@ -1540,7 +1441,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'test pinned event 2', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '44763500-7b6d-11e9-980a-e5349fc014ef', created: 1558404484133, createdBy: 'elastic', @@ -1552,7 +1452,6 @@ export const mockOpenTimelineQueryResults = { eventId: 'ZF0W12oB9v5HJNSHwY6L', note: 'Test pinned 1', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '3e9d51e0-7b6d-11e9-980a-e5349fc014ef', created: 1558404474317, createdBy: 'elastic', @@ -1564,7 +1463,6 @@ export const mockOpenTimelineQueryResults = { eventId: '4l0W12oB9v5HJNSHY4wv', note: 'again', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '48eaf440-7b6d-11e9-980a-e5349fc014ef', created: 1558404491600, createdBy: 'elastic', @@ -1576,7 +1474,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'Hello world', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '308783f0-7b6d-11e9-980a-e5349fc014ef', created: 1558404450688, createdBy: 'elastic', @@ -1588,7 +1485,6 @@ export const mockOpenTimelineQueryResults = { eventId: null, note: 'here I am', timelineId: '10849df0-7b44-11e9-a608-ab3d811602f9', - timelineVersion: null, noteId: '34ec1690-7b6d-11e9-980a-e5349fc014ef', created: 1558404458065, createdBy: 'elastic', diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx index 5c06c259803c5..4b6c3c053d0da 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx @@ -179,7 +179,7 @@ describe('dispatchUpdateTimeline', () => { updated: 1585233356356, noteId: 'note-id', note: 'I am a note', - timelineId: null, + timelineId: 'abc', version: 'testVersion', }, ], @@ -197,7 +197,7 @@ describe('dispatchUpdateTimeline', () => { note: 'I am a note', user: 'unknown', saveObjectId: 'note-id', - timelineId: null, + timelineId: 'abc', version: 'testVersion', }, ], diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 90acb50532653..0cd34f01fed33 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -148,10 +148,11 @@ const StatefulEventComponent: React.FC = ({ const activeTab = tabType ?? TimelineTabs.query; const activeExpandedDetail = expandedDetail[activeTab]; + const eventId = event._id; const isDetailPanelExpanded: boolean = (activeExpandedDetail?.panelView === 'eventDetail' && - activeExpandedDetail?.params?.eventId === event._id) || + activeExpandedDetail?.params?.eventId === eventId) || (activeExpandedDetail?.panelView === 'hostDetail' && activeExpandedDetail?.params?.hostName === hostName) || (activeExpandedDetail?.panelView === 'networkDetail' && @@ -161,7 +162,7 @@ const StatefulEventComponent: React.FC = ({ const getNotesByIds = useMemo(() => appSelectors.notesByIdsSelector(), []); const notesById = useDeepEqualSelector(getNotesByIds); - const noteIds: string[] = eventIdToNoteIds[event._id] || emptyNotes; + const noteIds: string[] = eventIdToNoteIds[eventId] || emptyNotes; const notes: TimelineResultNote[] = useMemo( () => @@ -181,8 +182,6 @@ const StatefulEventComponent: React.FC = ({ ); const onToggleShowNotes = useCallback(() => { - const eventId = event._id; - setShowNotes((prevShowNotes) => { if (prevShowNotes[eventId]) { // notes are closing, so focus the notes button on the next tick, after escaping the EuiFocusTrap @@ -196,10 +195,9 @@ const StatefulEventComponent: React.FC = ({ return { ...prevShowNotes, [eventId]: !prevShowNotes[eventId] }; }); - }, [event]); + }, [eventId]); const handleOnEventDetailPanelOpened = useCallback(() => { - const eventId = event._id; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const indexName = event._index!; @@ -235,7 +233,7 @@ const StatefulEventComponent: React.FC = ({ } }, [ dispatch, - event._id, + eventId, event._index, expandableTimelineFlyoutEnabled, isSecurityFlyoutEnabled, @@ -247,12 +245,15 @@ const StatefulEventComponent: React.FC = ({ const associateNote = useCallback( (noteId: string) => { - dispatch(timelineActions.addNoteToEvent({ eventId: event._id, id: timelineId, noteId })); - if (!isEventPinned) { - dispatch(timelineActions.pinEvent({ id: timelineId, eventId: event._id })); - } + dispatch( + timelineActions.addNoteToEvent({ + eventId, + id: timelineId, + noteId, + }) + ); }, - [dispatch, event, isEventPinned, timelineId] + [dispatch, eventId, timelineId] ); const setEventsLoading = useCallback( @@ -283,7 +284,7 @@ const StatefulEventComponent: React.FC = ({ showLeftBorder={!isEventViewer} > = ({ onRuleChange={onRuleChange} selectedEventIds={selectedEventIds} showCheckboxes={showCheckboxes} - showNotes={!!showNotes[event._id]} + showNotes={!!showNotes[eventId]} tabType={tabType} timelineId={timelineId} toggleShowNotes={onToggleShowNotes} @@ -323,7 +324,7 @@ const StatefulEventComponent: React.FC = ({ associateNote={associateNote} data-test-subj="note-cards" notes={notes} - showAddNote={!!showNotes[event._id]} + showAddNote={!!showNotes[eventId]} toggleShowAddNote={onToggleShowNotes} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 6c4f280c6fd46..bb45021107311 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -366,13 +366,6 @@ describe('Body', () => { }).type, }) ); - expect(mockDispatch).toHaveBeenNthCalledWith( - 3, - timelineActions.pinEvent({ - eventId: '1', - id: 'timeline-test', - }) - ); }); test('Add two notes to an event', async () => { @@ -385,7 +378,7 @@ describe('Body', () => { [TimelineId.test]: { ...mockGlobalState.timeline.timelineById[TimelineId.test], id: 'timeline-test', - pinnedEventIds: { 1: true }, // we should NOT dispatch a pin event, because it's already pinned + pinnedEventIds: { 1: true }, }, }, }, @@ -415,13 +408,6 @@ describe('Body', () => { }).type, }) ); - - expect(mockDispatch).not.toHaveBeenCalledWith( - timelineActions.pinEvent({ - eventId: '1', - id: 'timeline-test', - }) - ); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/pinned_event/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/pinned_event/api.ts index 4e5a7bc9a05a6..8eb149f0f43fb 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/pinned_event/api.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/pinned_event/api.ts @@ -15,7 +15,7 @@ export const persistPinnedEvent = async ({ }: { eventId: string; pinnedEventId?: string | null; - timelineId?: string | null; + timelineId: string; }) => { let requestBody; try { diff --git a/x-pack/plugins/security_solution/public/timelines/store/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/actions.ts index 16bcdd748bce2..bb38d56773d15 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/actions.ts @@ -37,9 +37,11 @@ const actionCreator = actionCreatorFactory('x-pack/security_solution/local/timel export const addNote = actionCreator<{ id: string; noteId: string }>('ADD_NOTE'); -export const addNoteToEvent = actionCreator<{ id: string; noteId: string; eventId: string }>( - 'ADD_NOTE_TO_EVENT' -); +export const addNoteToEvent = actionCreator<{ + id: string; + noteId: string; + eventId: string; +}>('ADD_NOTE_TO_EVENT'); export const deleteNoteFromEvent = actionCreator<{ id: string; noteId: string; eventId: string }>( 'DELETE_NOTE_FROM_EVENT' diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.test.ts new file mode 100644 index 0000000000000..e58d797c95120 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createMockStore, kibanaMock, mockGlobalState } from '../../../common/mock'; +import { TimelineId } from '../../../../common/types/timeline'; +import { TimelineStatus } from '../../../../common/api/timeline'; +import { persistTimeline } from '../../containers/api'; +import { ensureTimelineIsSaved } from './helpers'; + +jest.mock('../../containers/api'); + +describe('Timeline middleware helpers', () => { + describe('ensureTimelineIsSaved', () => { + let store = createMockStore(undefined, undefined, kibanaMock); + + beforeEach(() => { + store = createMockStore(undefined, undefined, kibanaMock); + jest.clearAllMocks(); + }); + + it('should return the given timeline if it has a `savedObjectId`', async () => { + const testTimeline = { + ...mockGlobalState.timeline.timelineById[TimelineId.test], + savedObjectId: '123', + }; + const returnedTimeline = await ensureTimelineIsSaved({ + localTimelineId: TimelineId.test, + timeline: testTimeline, + store, + }); + + expect(returnedTimeline).toBe(testTimeline); + }); + + it('should return a draft timeline with a savedObjectId when an unsaved timeline is passed', async () => { + const mockSavedObjectId = 'mockSavedObjectId'; + (persistTimeline as jest.Mock).mockResolvedValue({ + data: { + persistTimeline: { + code: 200, + message: 'success', + timeline: { + ...mockGlobalState.timeline.timelineById[TimelineId.test], + savedObjectId: mockSavedObjectId, + }, + }, + }, + }); + + const returnedTimeline = await ensureTimelineIsSaved({ + localTimelineId: TimelineId.test, + timeline: mockGlobalState.timeline.timelineById[TimelineId.test], + store, + }); + + expect(returnedTimeline.savedObjectId).toBe(mockSavedObjectId); + expect(returnedTimeline.status).toBe(TimelineStatus.draft); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts index 1123985bca77d..cf1d74e07051c 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts @@ -5,10 +5,15 @@ * 2.0. */ +import type { MiddlewareAPI, Dispatch, AnyAction } from 'redux'; import type { State } from '../../../common/store/types'; import { ALL_TIMELINE_QUERY_ID } from '../../containers/all'; import type { inputsModel } from '../../../common/store/inputs'; import { inputsSelectors } from '../../../common/store/inputs'; +import type { TimelineModel } from '../model'; +import { saveTimeline, updateTimeline } from '../actions'; +import { TimelineStatus } from '../../../../common/api/timeline'; +import { selectTimelineById } from '../selectors'; /** * Refreshes all timelines, so changes are propagated to everywhere on the page @@ -19,3 +24,41 @@ export function refreshTimelines(state: State) { (allTimelineQuery.refetch as inputsModel.Refetch)(); } } + +/** + * Given a timeline model, it will return that model when the timeline has been saved before, + * or save a draft version of that timeline. + * This is a usefull check for when you're working with timeline-associated saved objects + * which require the exitence of a timeline's `savedObjectId`. + */ +export async function ensureTimelineIsSaved({ + localTimelineId, + timeline, + store, +}: { + localTimelineId: string; + timeline: TimelineModel; + store: MiddlewareAPI, State>; +}) { + // In case `savedObjectId` exists, the timeline has been saved before. + if (timeline.savedObjectId) { + return timeline; + } + + // The timeline hasn't been saved, so let's create make it a draft. + await store.dispatch( + updateTimeline({ + id: localTimelineId, + timeline: { + ...timeline, + status: TimelineStatus.draft, + }, + }) + ); + + // The draft needs to be persisted + await store.dispatch(saveTimeline({ id: localTimelineId, saveAsNew: false })); + + // Make sure we're returning the most updated version of the timeline + return selectTimelineById(store.getState(), localTimelineId); +} diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts index 96aa0e58cc716..624ae513a418f 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { createMockStore, kibanaMock } from '../../../common/mock'; +import { createMockStore, kibanaMock, mockGlobalState } from '../../../common/mock'; import { selectTimelineById } from '../selectors'; import { TimelineId } from '../../../../common/types/timeline'; import { persistNote } from '../../containers/notes/api'; -import { refreshTimelines } from './helpers'; +import { refreshTimelines, ensureTimelineIsSaved } from './helpers'; import { startTimelineSaving, @@ -17,6 +17,7 @@ import { showCallOutUnauthorizedMsg, addNote, addNoteToEvent, + pinEvent, } from '../actions'; import { updateNote } from '../../../common/store/app/actions'; import { createNote } from '../../components/notes/helpers'; @@ -27,6 +28,7 @@ jest.mock('../actions', () => { (endTLSaving as unknown as { match: Function }).match = () => false; return { ...actual, + pinEvent: jest.fn((...args) => actual.pinEvent(...args)), showCallOutUnauthorizedMsg: jest .fn() .mockImplementation((...args) => actual.showCallOutUnauthorizedMsg(...args)), @@ -37,11 +39,24 @@ jest.mock('../actions', () => { }; }); jest.mock('../../containers/notes/api'); -jest.mock('./helpers'); +const mockTimelineSavedObjectId = 'mockTimelineSavedObjectId'; +jest.mock('./helpers', () => { + const actual = jest.requireActual('./helpers'); + return { + ...actual, + ensureTimelineIsSaved: jest.fn().mockImplementation(() => ({ + ...mockGlobalState.timeline.timelineById['timeline-test'], + savedObjectId: mockTimelineSavedObjectId, + })), + refreshTimelines: jest.fn(), + }; +}); const startTimelineSavingMock = startTimelineSaving as unknown as jest.Mock; const endTimelineSavingMock = endTimelineSaving as unknown as jest.Mock; const showCallOutUnauthorizedMsgMock = showCallOutUnauthorizedMsg as unknown as jest.Mock; +const pinEventMock = pinEvent as unknown as jest.Mock; +const ensureTimelineIsSavedMock = ensureTimelineIsSaved as unknown as jest.Mock; describe('Timeline note middleware', () => { let store = createMockStore(undefined, undefined, kibanaMock); @@ -103,6 +118,116 @@ describe('Timeline note middleware', () => { ); }); + it('should ensure the timeline is saved or in draft mode before creating a note', async () => { + (persistNote as jest.Mock).mockResolvedValue({ + data: { + persistNote: { + code: 200, + message: 'success', + note: { + noteId: testNote.id, + }, + }, + }, + }); + + expect(selectTimelineById(store.getState(), TimelineId.test)).toEqual( + expect.objectContaining({ + savedObjectId: null, + version: null, + }) + ); + await store.dispatch(updateNote({ note: testNote })); + await store.dispatch( + addNoteToEvent({ eventId: testEventId, id: TimelineId.test, noteId: testNote.id }) + ); + + expect(persistNote).toHaveBeenCalledWith( + expect.objectContaining({ + note: expect.objectContaining({ + timelineId: mockTimelineSavedObjectId, + }), + }) + ); + + expect(ensureTimelineIsSavedMock).toHaveBeenCalled(); + }); + + it('should pin the event when the event is not pinned yet', async () => { + const testTimelineId = 'testTimelineId'; + (persistNote as jest.Mock).mockResolvedValue({ + data: { + persistNote: { + code: 200, + message: 'success', + note: { + noteId: testNote.id, + timelineId: testTimelineId, + }, + }, + }, + }); + + await store.dispatch(updateNote({ note: testNote })); + await store.dispatch( + addNoteToEvent({ + eventId: testEventId, + id: TimelineId.test, + noteId: testNote.id, + }) + ); + + expect(pinEventMock).toHaveBeenCalledWith({ + eventId: testEventId, + id: TimelineId.test, + }); + }); + + it('should not pin the event when the event is already pinned', async () => { + store = createMockStore( + { + ...mockGlobalState, + timeline: { + ...mockGlobalState.timeline, + timelineById: { + [TimelineId.test]: { + ...mockGlobalState.timeline.timelineById[TimelineId.test], + pinnedEventIds: { + [testEventId]: true, + }, + }, + }, + }, + }, + undefined, + kibanaMock + ); + const testTimelineId = 'testTimelineId'; + (persistNote as jest.Mock).mockResolvedValue({ + data: { + persistNote: { + code: 200, + message: 'success', + note: { + noteId: testNote.id, + timelineId: testTimelineId, + }, + }, + }, + }); + + await store.dispatch(updateNote({ note: testNote })); + await store.dispatch( + addNoteToEvent({ + eventId: testEventId, + id: TimelineId.test, + noteId: testNote.id, + }) + ); + + expect(pinEventMock).not.toHaveBeenCalled(); + }); + it('should show an error message when the call is unauthorized', async () => { (persistNote as jest.Mock).mockResolvedValue({ data: { diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.ts index 5d77a5082b82c..876fd613d0791 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.ts @@ -19,12 +19,13 @@ import { endTimelineSaving, startTimelineSaving, showCallOutUnauthorizedMsg, + pinEvent, } from '../actions'; import { persistNote } from '../../containers/notes/api'; import type { ResponseNote } from '../../../../common/api/timeline'; import { selectTimelineById } from '../selectors'; import * as i18n from '../../pages/translations'; -import { refreshTimelines } from './helpers'; +import { ensureTimelineIsSaved, refreshTimelines } from './helpers'; type NoteAction = ReturnType; @@ -34,26 +35,42 @@ function isNoteAction(action: Action): action is NoteAction { return timelineNoteActionsType.has(action.type); } +function isAddNoteToEventAction(action: Action): action is ReturnType { + return action.type === addNoteToEvent.type; +} + export const addNoteToTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State> = (kibana: CoreStart) => (store) => (next) => async (action: Action) => { // perform the action const ret = next(action); if (isNoteAction(action)) { - const { id, noteId: localNoteId } = action.payload; - const timeline = selectTimelineById(store.getState(), id); + const { id: localTimelineId, noteId: localNoteId } = action.payload; const notes = appSelectors.selectNotesByIdSelector(store.getState()); - store.dispatch(startTimelineSaving({ id })); + store.dispatch(startTimelineSaving({ id: localTimelineId })); try { + // In case a note is being added to an unsaved timeline, we need to make sure + // the timeline has been saved or is in draft state. Otherwise, `timelineId` will be `null` + // and we're creating orphaned notes. + const timeline = await ensureTimelineIsSaved({ + localTimelineId, + timeline: selectTimelineById(store.getState(), localTimelineId), + store, + }); + + if (!timeline.savedObjectId) { + throw new Error('Cannot create note without a timelineId'); + } + const result = await persistNote({ noteId: null, version: null, note: { eventId: 'eventId' in action.payload ? action.payload.eventId : undefined, note: getNoteText(localNoteId, notes), - timelineId: timeline.id, + timelineId: timeline.savedObjectId, }, }); @@ -64,7 +81,7 @@ export const addNoteToTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, refreshTimelines(store.getState()); - store.dispatch( + await store.dispatch( updateNote({ note: { ...notes[localNoteId], @@ -79,6 +96,21 @@ export const addNoteToTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, }, }) ); + + const currentTimeline = selectTimelineById(store.getState(), localTimelineId); + + // Automatically pin an associated event if it's not pinned yet + if (isAddNoteToEventAction(action)) { + const isEventPinned = currentTimeline.pinnedEventIds[action.payload.eventId] === true; + if (!isEventPinned) { + await store.dispatch( + pinEvent({ + id: localTimelineId, + eventId: action.payload.eventId, + }) + ); + } + } } catch (error) { kibana.notifications.toasts.addDanger({ title: i18n.UPDATE_TIMELINE_ERROR_TITLE, @@ -87,7 +119,7 @@ export const addNoteToTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, } finally { store.dispatch( endTimelineSaving({ - id, + id: localTimelineId, }) ); } diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts index 3a6673886a9a9..4c303b1414515 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts @@ -5,19 +5,17 @@ * 2.0. */ -import { createMockStore, kibanaMock } from '../../../common/mock'; +import { createMockStore, kibanaMock, mockGlobalState } from '../../../common/mock'; import { selectTimelineById } from '../selectors'; import { TimelineId } from '../../../../common/types/timeline'; import { persistPinnedEvent } from '../../containers/pinned_event/api'; -import { refreshTimelines } from './helpers'; - +import { refreshTimelines, ensureTimelineIsSaved } from './helpers'; import { startTimelineSaving, endTimelineSaving, pinEvent, unPinEvent, showCallOutUnauthorizedMsg, - updateTimeline, } from '../actions'; jest.mock('../actions', () => { @@ -36,11 +34,23 @@ jest.mock('../actions', () => { }; }); jest.mock('../../containers/pinned_event/api'); -jest.mock('./helpers'); +const mockTimelineSavedObjectId = 'mockTimelineSavedObjectId'; +jest.mock('./helpers', () => { + const actual = jest.requireActual('./helpers'); + return { + ...actual, + ensureTimelineIsSaved: jest.fn().mockImplementation(() => ({ + ...mockGlobalState.timeline.timelineById['timeline-test'], + savedObjectId: mockTimelineSavedObjectId, + })), + refreshTimelines: jest.fn(), + }; +}); const startTimelineSavingMock = startTimelineSaving as unknown as jest.Mock; const endTimelineSavingMock = endTimelineSaving as unknown as jest.Mock; const showCallOutUnauthorizedMsgMock = showCallOutUnauthorizedMsg as unknown as jest.Mock; +const ensureTimelineIsSavedMock = ensureTimelineIsSaved as unknown as jest.Mock; describe('Timeline pinned event middleware', () => { let store = createMockStore(undefined, undefined, kibanaMock); @@ -71,17 +81,26 @@ describe('Timeline pinned event middleware', () => { }); it('should persist a timeline un-pin event', async () => { - store.dispatch( - updateTimeline({ - id: TimelineId.test, + store = createMockStore( + { + ...mockGlobalState, timeline: { - ...selectTimelineById(store.getState(), TimelineId.test), - pinnedEventIds: { - [testEventId]: true, + ...mockGlobalState.timeline, + timelineById: { + ...mockGlobalState.timeline.timelineById, + [TimelineId.test]: { + ...mockGlobalState.timeline.timelineById[TimelineId.test], + pinnedEventIds: { + [testEventId]: true, + }, + }, }, }, - }) + }, + undefined, + kibanaMock ); + (persistPinnedEvent as jest.Mock).mockResolvedValue({ data: {}, }); @@ -96,6 +115,28 @@ describe('Timeline pinned event middleware', () => { expect(selectTimelineById(store.getState(), TimelineId.test).pinnedEventIds).toEqual({}); }); + it('should ensure the timeline is saved or in draft mode before pinning an event', async () => { + (persistPinnedEvent as jest.Mock).mockResolvedValue({ + data: { + persistPinnedEventOnTimeline: { + code: 200, + }, + }, + }); + expect(selectTimelineById(store.getState(), TimelineId.test).pinnedEventIds).toEqual({}); + await store.dispatch(pinEvent({ id: TimelineId.test, eventId: testEventId })); + + expect(persistPinnedEvent).toHaveBeenCalledWith( + expect.objectContaining({ + timelineId: mockTimelineSavedObjectId, + eventId: testEventId, + pinnedEventId: null, + }) + ); + + expect(ensureTimelineIsSavedMock).toHaveBeenCalled(); + }); + it('should show an error message when the call is unauthorized', async () => { (persistPinnedEvent as jest.Mock).mockResolvedValue({ data: { diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts index 9e7b5b772489d..c26c458042dad 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts @@ -22,7 +22,7 @@ import { showCallOutUnauthorizedMsg, } from '../actions'; import { persistPinnedEvent } from '../../containers/pinned_event/api'; -import { refreshTimelines } from './helpers'; +import { ensureTimelineIsSaved, refreshTimelines } from './helpers'; type PinnedEventAction = ReturnType; @@ -39,18 +39,30 @@ export const addPinnedEventToTimelineMiddleware: (kibana: CoreStart) => Middlewa if (isPinnedEventAction(action)) { const { id: localTimelineId, eventId } = action.payload; - const timeline = selectTimelineById(store.getState(), localTimelineId); store.dispatch(startTimelineSaving({ id: localTimelineId })); try { + // In case an event is pinned on an unsaved timeline, we need to make sure + // the timeline has been saved or is in draft state. Otherwise, `timelineId` will be `null` + // and we're creating orphaned pinned events. + const timeline = await ensureTimelineIsSaved({ + localTimelineId, + timeline: selectTimelineById(store.getState(), localTimelineId), + store, + }); + + if (!timeline.savedObjectId) { + throw new Error('Cannot create a pinned event without a timelineId'); + } + const result = await persistPinnedEvent({ pinnedEventId: timeline.pinnedEventsSaveObject[eventId] != null ? timeline.pinnedEventsSaveObject[eventId].pinnedEventId : null, eventId, - timelineId: timeline.id, + timelineId: timeline.savedObjectId, }); const response: PinnedEventResponse = get('data.persistPinnedEventOnTimeline', result); @@ -60,34 +72,37 @@ export const addPinnedEventToTimelineMiddleware: (kibana: CoreStart) => Middlewa refreshTimelines(store.getState()); + const currentTimeline = selectTimelineById(store.getState(), action.payload.id); // The response is null in case we unpinned an event. // In that case we want to remove the locally pinned event. if (!response) { - store.dispatch( + return store.dispatch( updateTimeline({ id: action.payload.id, timeline: { - ...timeline, - pinnedEventIds: omit(eventId, timeline.pinnedEventIds), - pinnedEventsSaveObject: omit(eventId, timeline.pinnedEventsSaveObject), + ...currentTimeline, + pinnedEventIds: omit(eventId, currentTimeline.pinnedEventIds), + pinnedEventsSaveObject: omit(eventId, currentTimeline.pinnedEventsSaveObject), }, }) ); } else { - store.dispatch( + const updatedTimeline = { + ...currentTimeline, + pinnedEventIds: { + ...currentTimeline.pinnedEventIds, + [eventId]: true, + }, + pinnedEventsSaveObject: { + ...currentTimeline.pinnedEventsSaveObject, + [eventId]: response, + }, + }; + + await store.dispatch( updateTimeline({ id: action.payload.id, - timeline: { - ...timeline, - pinnedEventIds: { - ...timeline.pinnedEventIds, - [eventId]: true, - }, - pinnedEventsSaveObject: { - ...timeline.pinnedEventsSaveObject, - [eventId]: response, - }, - }, + timeline: updatedTimeline, }) ); } diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts index 177f8bc530471..acd28651e23cb 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts @@ -116,7 +116,7 @@ describe('clean draft timelines', () => { expect(mockPersistTimeline).not.toHaveBeenCalled(); expect(mockResetTimeline).toHaveBeenCalled(); - expect(mockResetTimeline.mock.calls[0][1]).toEqual([mockGetDraftTimelineValue.savedObjectId]); + expect(mockResetTimeline.mock.calls[0][1]).toEqual(mockGetDraftTimelineValue.savedObjectId); expect(mockResetTimeline.mock.calls[0][2]).toEqual(req.body.timelineType); expect(mockGetTimeline).toHaveBeenCalled(); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts index dff25caef5f26..604e9dabe0d62 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts @@ -56,7 +56,7 @@ export const cleanDraftTimelinesRoute = ( if (draftTimeline?.savedObjectId) { await resetTimeline( frameworkRequest, - [draftTimeline.savedObjectId], + draftTimeline.savedObjectId, request.body.timelineType ); const cleanedDraftTimeline = await getTimeline( diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts index 07d37a28905e7..a6b502111f7cc 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts @@ -53,7 +53,7 @@ export const persistNoteRoute = ( noteId, note: { ...note, - timelineId: note.timelineId || null, + timelineId: note.timelineId, }, overrideOwner: true, }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.test.ts index 1fbc51871447f..fef377a91450d 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.test.ts @@ -34,7 +34,7 @@ describe('saved_object', () => { }); describe('Set create / update time correctly ', () => { - test('Creating a timeline', () => { + test('Creating a note', () => { const savedNote = getMockSavedNote(); const noteId = null; const userInfo = { username: 'elastic' } as AuthenticatedUser; @@ -44,7 +44,7 @@ describe('saved_object', () => { expect(result.updated).toEqual(mockDateNow); }); - test('Updating a timeline', () => { + test('Updating a note', () => { const savedNote = getMockSavedNote(); const noteId = savedNote.noteId ?? null; const userInfo = { username: 'elastic' } as AuthenticatedUser; @@ -56,7 +56,7 @@ describe('saved_object', () => { }); describe('Set userInfo correctly ', () => { - test('Creating a timeline', () => { + test('Creating a note', () => { const savedNote = getMockSavedNote(); const noteId = null; const userInfo = { username: 'elastic' } as AuthenticatedUser; @@ -66,7 +66,7 @@ describe('saved_object', () => { expect(result.updatedBy).toEqual(userInfo.username); }); - test('Creating a timeline with user email', () => { + test('Creating a note with user email', () => { const savedNote = getMockSavedNote(); const noteId = null; const userInfo = { username: 'elastic', email: 'some@email.com' } as AuthenticatedUser; @@ -76,7 +76,7 @@ describe('saved_object', () => { expect(result.updatedBy).toEqual(userInfo.email); }); - test('Creating a timeline with user full name', () => { + test('Creating a note with user full name', () => { const savedNote = getMockSavedNote(); const noteId = null; const userInfo = { @@ -90,7 +90,7 @@ describe('saved_object', () => { expect(result.updatedBy).toEqual(userInfo.full_name); }); - test('Updating a timeline', () => { + test('Updating a note', () => { const savedNote = getMockSavedNote(); const noteId = savedNote.noteId ?? null; const userInfo = { username: 'elastic' } as AuthenticatedUser; @@ -100,7 +100,7 @@ describe('saved_object', () => { expect(result.updatedBy).toEqual(userInfo.username); }); - test('Updating a timeline with user email', () => { + test('Updating a note with user email', () => { const savedNote = getMockSavedNote(); const noteId = savedNote.noteId ?? null; const userInfo = { username: 'elastic', email: 'some@email.com' } as AuthenticatedUser; @@ -110,7 +110,7 @@ describe('saved_object', () => { expect(result.updatedBy).toEqual(userInfo.email); }); - test('Updating a timeline with user full name', () => { + test('Updating a note with user full name', () => { const savedNote = getMockSavedNote(); const noteId = savedNote.noteId ?? null; const userInfo = { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts index 8527b0970f4fd..18b6e19dfcb00 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts @@ -27,11 +27,10 @@ import { SavedObjectNoteRuntimeType } from '../../../../../common/types/timeline import type { SavedObjectNoteWithoutExternalRefs } from '../../../../../common/types/timeline/note/saved_object'; import type { FrameworkRequest } from '../../../framework'; import { noteSavedObjectType } from '../../saved_object_mappings/notes'; -import { createTimeline } from '../timelines'; import { timelineSavedObjectType } from '../../saved_object_mappings'; import { noteFieldsMigrator } from './field_migrator'; -export const deleteNoteByTimelineId = async (request: FrameworkRequest, timelineId: string) => { +export const deleteNotesByTimelineId = async (request: FrameworkRequest, timelineId: string) => { const options: SavedObjectsFindOptions = { type: noteSavedObjectType, hasReference: { type: timelineSavedObjectType, id: timelineId }, @@ -106,7 +105,6 @@ export const persistNote = async ({ noteId: uuidv1(), version: '', timelineId: '', - timelineVersion: '', }; return { code: 403, @@ -132,24 +130,7 @@ const createNote = async ({ const savedObjectsClient = (await request.context.core).savedObjects.client; const userInfo = request.user; - const shallowCopyOfNote = { ...note }; - let timelineVersion: string | undefined; - - if (note.timelineId == null) { - const { timeline: timelineResult } = await createTimeline({ - timelineId: null, - timeline: {}, - savedObjectsClient, - userInfo, - }); - - shallowCopyOfNote.timelineId = timelineResult.savedObjectId; - timelineVersion = timelineResult.version; - } - - const noteWithCreator = overrideOwner - ? pickSavedNote(noteId, shallowCopyOfNote, userInfo) - : shallowCopyOfNote; + const noteWithCreator = overrideOwner ? pickSavedNote(noteId, { ...note }, userInfo) : note; const { transformedFields: migratedAttributes, references } = noteFieldsMigrator.extractFieldsToReferences({ @@ -175,7 +156,7 @@ const createNote = async ({ const repopulatedSavedObject = noteFieldsMigrator.populateFieldsFromReferences(createdNote); - const convertedNote = convertSavedObjectToSavedNote(repopulatedSavedObject, timelineVersion); + const convertedNote = convertSavedObjectToSavedNote(repopulatedSavedObject); // Create new note return { @@ -266,17 +247,13 @@ const getAllSavedNote = async (request: FrameworkRequest, options: SavedObjectsF }; }; -export const convertSavedObjectToSavedNote = ( - savedObject: unknown, - timelineVersion?: string | undefined | null -): Note => +export const convertSavedObjectToSavedNote = (savedObject: unknown): Note => pipe( SavedObjectNoteRuntimeType.decode(savedObject), map((savedNote) => { return { noteId: savedNote.id, version: savedNote.version, - timelineVersion, timelineId: savedNote.attributes.timelineId, eventId: savedNote.attributes.eventId, note: savedNote.attributes.note, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts index 96f2aa3d1c26b..4cb37fd6d6d89 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts @@ -24,7 +24,6 @@ import { SavedObjectPinnedEventRuntimeType } from '../../../../../common/types/t import type { SavedObjectPinnedEventWithoutExternalRefs } from '../../../../../common/types/timeline/pinned_event/saved_object'; import type { FrameworkRequest } from '../../../framework'; -import { createTimeline } from '../timelines'; import { pinnedEventSavedObjectType } from '../../saved_object_mappings/pinned_events'; import { pinnedEventFieldsMigrator } from './field_migrator'; import { timelineSavedObjectType } from '../../saved_object_mappings'; @@ -49,8 +48,7 @@ export const deleteAllPinnedEventsOnTimeline = async ( const savedObjectsClient = (await request.context.core).savedObjects.client; const options: SavedObjectsFindOptions = { type: pinnedEventSavedObjectType, - search: timelineId, - searchFields: ['timelineId'], + hasReference: { type: timelineSavedObjectType, id: timelineId }, }; const pinnedEventToBeDeleted = await getAllSavedPinnedEvents(request, options); await Promise.all( @@ -78,7 +76,7 @@ export const persistPinnedEventOnTimeline = async ( request: FrameworkRequest, pinnedEventId: string | null, // pinned event saved object id eventId: string, - timelineId: string | null + timelineId: string ): Promise => { try { if (pinnedEventId != null) { @@ -87,16 +85,7 @@ export const persistPinnedEventOnTimeline = async ( return null; } - const { timelineId: validatedTimelineId, timelineVersion } = await getValidTimelineIdAndVersion( - request, - timelineId - ); - - const pinnedEvents = await getPinnedEventsInTimelineWithEventId( - request, - validatedTimelineId, - eventId - ); + const pinnedEvents = await getPinnedEventsInTimelineWithEventId(request, timelineId, eventId); // we already had this event pinned so let's just return the one we already had if (pinnedEvents.length > 0) { @@ -106,8 +95,7 @@ export const persistPinnedEventOnTimeline = async ( return await createPinnedEvent({ request, eventId, - timelineId: validatedTimelineId, - timelineVersion, + timelineId, }); } catch (err) { if (getOr(null, 'output.statusCode', err) === 404) { @@ -124,7 +112,6 @@ export const persistPinnedEventOnTimeline = async ( message: err.message, pinnedEventId: eventId, timelineId: '', - timelineVersion: '', version: '', eventId: '', } @@ -134,32 +121,6 @@ export const persistPinnedEventOnTimeline = async ( } }; -const getValidTimelineIdAndVersion = async ( - request: FrameworkRequest, - timelineId: string | null -): Promise<{ timelineId: string; timelineVersion?: string }> => { - if (timelineId != null) { - return { - timelineId, - }; - } - - const savedObjectsClient = (await request.context.core).savedObjects.client; - - // create timeline because it didn't exist - const { timeline: timelineResult } = await createTimeline({ - timelineId: null, - timeline: {}, - savedObjectsClient, - userInfo: request.user, - }); - - return { - timelineId: timelineResult.savedObjectId, - timelineVersion: timelineResult.version, - }; -}; - const getPinnedEventsInTimelineWithEventId = async ( request: FrameworkRequest, timelineId: string, @@ -175,12 +136,10 @@ const createPinnedEvent = async ({ request, eventId, timelineId, - timelineVersion, }: { request: FrameworkRequest; eventId: string; timelineId: string; - timelineVersion?: string; }): Promise => { const savedObjectsClient = (await request.context.core).savedObjects.client; @@ -216,7 +175,7 @@ const createPinnedEvent = async ({ // create Pinned Event on Timeline return { - ...convertSavedObjectToSavedPinnedEvent(repopulatedSavedObject, timelineVersion), + ...convertSavedObjectToSavedPinnedEvent(repopulatedSavedObject), code: 200, }; }; @@ -254,17 +213,13 @@ export const savePinnedEvents = ( ) ); -export const convertSavedObjectToSavedPinnedEvent = ( - savedObject: unknown, - timelineVersion?: string | undefined | null -): PinnedEvent => +export const convertSavedObjectToSavedPinnedEvent = (savedObject: unknown): PinnedEvent => pipe( SavedObjectPinnedEventRuntimeType.decode(savedObject), map((savedPinnedEvent) => { return { pinnedEventId: savedPinnedEvent.id, version: savedPinnedEvent.version, - timelineVersion, timelineId: savedPinnedEvent.attributes.timelineId, created: savedPinnedEvent.attributes.created, createdBy: savedPinnedEvent.attributes.createdBy, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts index 037639464a3e8..6044516c49e26 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts @@ -548,27 +548,18 @@ export const updatePartialSavedTimeline = async ( export const resetTimeline = async ( request: FrameworkRequest, - timelineIds: string[], + timelineId: string, timelineType: TimelineType ) => { - if (!timelineIds.length) { - return Promise.reject(new Error('timelineIds is empty')); - } - - await Promise.all( - timelineIds.map((timelineId) => - Promise.all([ - note.deleteNoteByTimelineId(request, timelineId), - pinnedEvent.deleteAllPinnedEventsOnTimeline(request, timelineId), - ]) - ) - ); + await Promise.all([ + note.deleteNotesByTimelineId(request, timelineId), + pinnedEvent.deleteAllPinnedEventsOnTimeline(request, timelineId), + ]); - const response = await Promise.all( - timelineIds.map((timelineId) => - updatePartialSavedTimeline(request, timelineId, { ...draftTimelineDefaults, timelineType }) - ) - ); + const response = await updatePartialSavedTimeline(request, timelineId, { + ...draftTimelineDefaults, + timelineType, + }); return response; }; @@ -584,7 +575,7 @@ export const deleteTimeline = async ( ...timelineIds.map((timelineId) => Promise.all([ savedObjectsClient.delete(timelineSavedObjectType, timelineId), - note.deleteNoteByTimelineId(request, timelineId), + note.deleteNotesByTimelineId(request, timelineId), pinnedEvent.deleteAllPinnedEventsOnTimeline(request, timelineId), ]) ), diff --git a/x-pack/test/api_integration/apis/security_solution/saved_objects/draft_timeline.ts b/x-pack/test/api_integration/apis/security_solution/saved_objects/draft_timeline.ts new file mode 100644 index 0000000000000..40e323b3c37f5 --- /dev/null +++ b/x-pack/test/api_integration/apis/security_solution/saved_objects/draft_timeline.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const supertest = getService('supertest'); + + describe('Draft timeline - Saved Objects', () => { + before(() => kibanaServer.savedObjects.cleanStandardList()); + after(() => kibanaServer.savedObjects.cleanStandardList()); + + describe('Clean draft timelines', () => { + it('returns a draft timeline if none exists', async () => { + const response = await supertest + .post('/api/timeline/_draft') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ + timelineType: 'default', + }); + + const { savedObjectId, version } = + response.body.data && response.body.data.persistTimeline.timeline; + + expect(savedObjectId).to.not.be.empty(); + expect(version).to.not.be.empty(); + }); + + it('returns a draft timeline template if none exists', async () => { + const response = await supertest + .post('/api/timeline/_draft') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ + timelineType: 'template', + }); + + const { + savedObjectId, + version, + timelineType, + templateTimelineId, + templateTimelineVersion, + } = response.body.data && response.body.data.persistTimeline.timeline; + + expect(savedObjectId).to.not.be.empty(); + expect(version).to.not.be.empty(); + expect(timelineType).to.be.equal('template'); + expect(templateTimelineVersion).to.not.be.equal(null); + expect(templateTimelineId).to.not.be.empty(); + }); + + it('returns a cleaned draft timeline if another one already exists', async () => { + const response = await supertest + .post('/api/timeline/_draft') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ + timelineType: 'default', + }); + + const { + savedObjectId: initialSavedObjectId, + pinnedEventIds: initialPinnedEventIds, + noteIds: initialNoteIds, + version: initialVersion, + } = response.body.data && response.body.data.persistTimeline.timeline; + + expect(initialPinnedEventIds).to.have.length(0, 'should not have any pinned events'); + expect(initialNoteIds).to.have.length(0, 'should not have any notes'); + + // Adding notes and pinned events + await supertest + .patch('/api/note') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ + noteId: null, + version: null, + note: { note: 'test note', timelineId: initialSavedObjectId }, + }); + await supertest + .patch('/api/pinned_event') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ + pinnedEventId: null, + timelineId: initialSavedObjectId, + eventId: 'bv4QSGsB9v5HJNSH-7fi', + }); + + const getTimelineRequest = await supertest + .get(`/api/timeline?id=${initialSavedObjectId}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(); + + const { + pinnedEventIds, + noteIds, + status: newStatus, + } = getTimelineRequest.body.data && getTimelineRequest.body.data.getOneTimeline; + + expect(newStatus).to.be.equal('draft', 'status should still be draft'); + expect(pinnedEventIds).to.have.length(1, 'should have one pinned event'); + expect(noteIds).to.have.length(1, 'should have one note'); + + const cleanDraftTimelineRequest = await supertest + .post('/api/timeline/_draft') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ + timelineType: 'default', + }); + + const { + savedObjectId: cleanedSavedObjectId, + pinnedEventIds: cleanedPinnedEventIds, + noteIds: cleanedNoteIds, + version: cleanedVersion, + } = cleanDraftTimelineRequest.body.data && + cleanDraftTimelineRequest.body.data.persistTimeline.timeline; + + expect(cleanedPinnedEventIds).to.have.length(0, 'should not have pinned events anymore'); + expect(cleanedNoteIds).to.have.length(0, 'should not have notes anymore'); + expect(cleanedSavedObjectId).to.be.equal( + initialSavedObjectId, + 'the savedObjectId should not have changed' + ); + expect(cleanedVersion).not.to.be.equal( + initialVersion, + 'should have a different version than initially' + ); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/security_solution/saved_objects/notes.ts b/x-pack/test/api_integration/apis/security_solution/saved_objects/notes.ts index a6dc1f88c596e..80db8b736dac3 100644 --- a/x-pack/test/api_integration/apis/security_solution/saved_objects/notes.ts +++ b/x-pack/test/api_integration/apis/security_solution/saved_objects/notes.ts @@ -18,24 +18,24 @@ export default function ({ getService }: FtrProviderContext) { after(() => kibanaServer.savedObjects.cleanStandardList()); describe('create a note', () => { - it('should return a timelineId, timelineVersion, noteId and version', async () => { + it('should return a timelineId, noteId and version', async () => { const myNote = 'world test'; const response = await supertest .patch('/api/note') .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') .send({ noteId: null, version: null, - note: { note: myNote, timelineId: null }, + note: { note: myNote, timelineId: 'testTimelineId' }, }); - const { note, noteId, timelineId, timelineVersion, version } = + const { note, noteId, timelineId, version } = response.body.data && response.body.data.persistNote.note; expect(note).to.be(myNote); expect(noteId).to.not.be.empty(); expect(timelineId).to.not.be.empty(); - expect(timelineVersion).to.not.be.empty(); expect(version).to.not.be.empty(); }); @@ -44,10 +44,11 @@ export default function ({ getService }: FtrProviderContext) { const response = await supertest .patch('/api/note') .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') .send({ noteId: null, version: null, - note: { note: myNote, timelineId: null }, + note: { note: myNote, timelineId: 'testTimelineId' }, }); const { noteId, timelineId, version } = @@ -57,6 +58,7 @@ export default function ({ getService }: FtrProviderContext) { const responseToTest = await supertest .patch('/api/note') .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') .send({ noteId, version, diff --git a/x-pack/test/api_integration/apis/security_solution/saved_objects/pinned_events.ts b/x-pack/test/api_integration/apis/security_solution/saved_objects/pinned_events.ts index dcfe8a109b04e..f7a16989b692d 100644 --- a/x-pack/test/api_integration/apis/security_solution/saved_objects/pinned_events.ts +++ b/x-pack/test/api_integration/apis/security_solution/saved_objects/pinned_events.ts @@ -18,37 +18,48 @@ export default function ({ getService }: FtrProviderContext) { after(() => kibanaServer.savedObjects.cleanStandardList()); describe('Pinned an event', () => { - it('return a timelineId, timelineVersion, pinnedEventId and version', async () => { - const response = await supertest.patch('/api/pinned_event').set('kbn-xsrf', 'true').send({ - pinnedEventId: null, - eventId: 'bv4QSGsB9v5HJNSH-7fi', - }); - const { eventId, pinnedEventId, timelineId, timelineVersion, version } = + it('return a timelineId, pinnedEventId and version', async () => { + const response = await supertest + .patch('/api/pinned_event') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ + pinnedEventId: null, + timelineId: 'testId', + eventId: 'bv4QSGsB9v5HJNSH-7fi', + }); + const { eventId, pinnedEventId, timelineId, version } = response.body.data && response.body.data.persistPinnedEventOnTimeline; expect(eventId).to.be('bv4QSGsB9v5HJNSH-7fi'); expect(pinnedEventId).to.not.be.empty(); - expect(timelineId).to.not.be.empty(); - expect(timelineVersion).to.not.be.empty(); + expect(timelineId).to.be('testId'); expect(version).to.not.be.empty(); }); }); describe('Unpinned an event', () => { it('return null', async () => { - const response = await supertest.patch('/api/pinned_event').set('kbn-xsrf', 'true').send({ - pinnedEventId: null, - eventId: 'bv4QSGsB9v5HJNSH-7fi', - }); - const { eventId, pinnedEventId } = + const response = await supertest + .patch('/api/pinned_event') + .set('elastic-api-version', '2023-10-31') + .set('kbn-xsrf', 'true') + .send({ + pinnedEventId: null, + eventId: 'bv4QSGsB9v5HJNSH-7fi', + timelineId: 'testId', + }); + const { eventId, pinnedEventId, timelineId } = response.body.data && response.body.data.persistPinnedEventOnTimeline; const responseToTest = await supertest .patch('/api/pinned_event') .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') .send({ pinnedEventId, eventId, + timelineId, }); expect(responseToTest.body.data!.persistPinnedEventOnTimeline).to.be(null); });