From 1186ed45044d43cc65e48df58221ef80059ff5b6 Mon Sep 17 00:00:00 2001 From: Jan Monschke Date: Tue, 1 Oct 2024 19:06:41 +0200 Subject: [PATCH] [8.x] [Threat Hunting Investigations] Migrate all timeline routes to OpenAPI types (#190238) (#194606) # Backport This will backport the following commits from `main` to `8.x`: - [[Threat Hunting Investigations] Migrate all timeline routes to OpenAPI types (#190238)](https://github.com/elastic/kibana/pull/190238) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) --- .../output/kibana.serverless.staging.yaml | 425 +++++++------ oas_docs/output/kibana.staging.yaml | 425 +++++++------ .../common/api/quickstart_client.gen.ts | 24 + .../clean_draft_timelines_route.gen.ts | 10 +- .../clean_draft_timelines_route.schema.yaml | 21 +- .../copy_timeline/copy_timeline_route.gen.ts | 29 + .../copy_timeline_route.schema.yaml | 34 + .../copy_timeline/copy_timeline_route.ts | 15 - .../create_timelines_route.gen.ts | 14 +- .../create_timelines_route.schema.yaml | 27 +- .../create_timelines_route.ts | 36 -- .../delete_note/delete_note_route.schema.yaml | 7 - .../delete_timelines_route.schema.yaml | 7 - .../export_timelines_route.schema.yaml | 7 - .../get_draft_timelines_route.gen.ts | 10 +- .../get_draft_timelines_route.schema.yaml | 21 +- .../get_draft_timelines_route.ts | 14 - .../get_notes/get_notes_route.schema.yaml | 7 - .../get_timeline/get_timeline_route.gen.ts | 11 +- .../get_timeline_route.schema.yaml | 27 +- .../get_timeline/get_timeline_route.ts | 15 - .../get_timelines/get_timelines_route.gen.ts | 16 +- .../get_timelines_route.schema.yaml | 57 +- .../get_timelines/get_timelines_route.ts | 28 - .../import_timelines_route.gen.ts | 17 +- .../import_timelines_route.schema.yaml | 39 +- .../import_timelines_route.ts | 60 -- .../common/api/timeline/index.ts | 10 - .../install_prepackaged_timelines.ts | 19 - ...install_prepackaged_timelines_route.gen.ts | 12 +- ...ll_prepackaged_timelines_route.schema.yaml | 16 +- .../common/api/timeline/model/api.ts | 593 ++---------------- .../api/timeline/model/components.gen.ts | 172 +++-- .../api/timeline/model/components.schema.yaml | 220 +++++-- .../patch_timeline_route.gen.ts | 10 +- .../patch_timeline_route.schema.yaml | 21 +- .../patch_timelines/patch_timelines_schema.ts | 24 - .../persist_favorite_route.schema.yaml | 7 - .../persist_note_route.schema.yaml | 7 - .../pinned_events_route.schema.yaml | 7 - .../pinned_events/pinned_events_route.ts | 34 - .../resolve_timeline_route.gen.ts | 11 +- .../resolve_timeline_route.schema.yaml | 24 +- .../common/api/timeline/routes.ts | 50 +- .../common/search_strategy/timeline/index.ts | 124 +--- .../common/timelines/zod_errors.ts | 29 + ...imeline_api_2023_10_31.bundled.schema.yaml | 404 ++++++------ ...imeline_api_2023_10_31.bundled.schema.yaml | 404 ++++++------ .../public/common/mock/timeline_results.ts | 9 +- .../components/alerts_table/actions.tsx | 14 +- .../components/open_timeline/helpers.test.ts | 22 +- .../components/open_timeline/helpers.ts | 24 +- .../components/open_timeline/types.ts | 8 +- .../public/timelines/containers/all/index.tsx | 22 +- .../public/timelines/containers/api.ts | 157 ++--- .../public/timelines/containers/helpers.ts | 4 +- .../middlewares/timeline_pinned_event.test.ts | 1 + .../middlewares/timeline_pinned_event.ts | 2 +- .../store/middlewares/timeline_save.test.ts | 4 +- .../store/middlewares/timeline_save.ts | 23 +- ...ebuilt_rules_and_timelines_status_route.ts | 9 +- .../logic/perform_timelines_installation.ts | 10 +- .../timeline/__mocks__/request_responses.ts | 13 +- .../timeline/__mocks__/resolve_timeline.ts | 4 +- .../clean_draft_timelines/index.ts | 7 +- .../get_draft_timelines/index.ts | 15 +- .../server/lib/timeline/routes/index.ts | 28 +- .../lib/timeline/routes/notes/delete_note.ts | 10 +- .../lib/timeline/routes/notes/get_notes.ts | 10 +- .../lib/timeline/routes/notes/persist_note.ts | 7 +- .../pinned_events/persist_pinned_event.ts | 22 +- .../helpers.test.ts | 6 +- .../install_prepackaged_timelines/helpers.ts | 6 +- .../install_prepackaged_timelines/index.ts | 32 +- .../routes/timelines/copy_timeline/index.ts | 16 +- .../timelines/create_timelines/helpers.ts | 7 +- .../timelines/create_timelines/index.ts | 14 +- .../timelines/delete_timelines/index.ts | 6 +- .../timelines/export_timelines/helpers.ts | 6 +- .../timelines/get_timeline/index.test.ts | 8 +- .../routes/timelines/get_timeline/index.ts | 23 +- .../timelines/get_timelines/index.test.ts | 8 +- .../routes/timelines/get_timelines/index.ts | 45 +- .../create_timelines_stream_from_ndjson.ts | 24 +- .../timelines/import_timelines/helpers.ts | 20 +- .../timelines/import_timelines/index.test.ts | 47 +- .../timelines/import_timelines/index.ts | 32 +- .../routes/timelines/patch_timelines/index.ts | 16 +- .../timelines/persist_favorite/index.ts | 11 +- .../timelines/resolve_timeline/index.ts | 24 +- .../saved_object/notes/persist_notes.ts | 2 +- .../convert_saved_object_to_savedtimeline.ts | 4 +- .../saved_object/timelines/index.test.ts | 8 +- .../timeline/saved_object/timelines/index.ts | 87 +-- .../timelines/pick_saved_timeline.ts | 6 +- .../timeline/utils/check_timelines_status.ts | 24 +- .../server/lib/timeline/utils/common.ts | 9 +- .../lib/timeline/utils/failure_cases.test.ts | 40 +- .../lib/timeline/utils/failure_cases.ts | 38 +- .../lib/timeline/utils/timeline_object.ts | 4 +- .../services/security_solution_api.gen.ts | 16 + .../trial_license_complete_tier/timeline.ts | 6 +- .../cypress/objects/timeline.ts | 8 +- .../cypress/tasks/api_calls/timelines.ts | 13 +- .../endpoint_solution_integrations.ts | 4 +- .../services/timeline/index.ts | 28 +- 106 files changed, 2013 insertions(+), 2661 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.gen.ts create mode 100644 x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.schema.yaml delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timelines_schema.ts delete mode 100644 x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.ts create mode 100644 x-pack/plugins/security_solution/common/timelines/zod_errors.ts diff --git a/oas_docs/output/kibana.serverless.staging.yaml b/oas_docs/output/kibana.serverless.staging.yaml index 42519812bbc59..87f4d3a227dd6 100644 --- a/oas_docs/output/kibana.serverless.staging.yaml +++ b/oas_docs/output/kibana.serverless.staging.yaml @@ -16233,19 +16233,21 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - nullable: true + data: + type: object + properties: + getOneTimeline: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineResponse + required: + - getOneTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: Indicates that the (template) timeline was found and returned. summary: >- Get an existing saved timeline or timeline template. This API is used to @@ -16284,23 +16286,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: >- Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft @@ -16361,21 +16348,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: Indicates the timeline was successfully created. '405': content: @@ -16392,6 +16366,37 @@ paths: tags: - Security Timeline API - 'access:securitySolution' + /api/timeline/_copy: + get: + description: | + Copies and returns a timeline or timeline template. + operationId: CopyTimeline + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + type: object + properties: + timeline: + $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + timelineIdToCopy: + type: string + required: + - timeline + - timelineIdToCopy + required: true + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse + description: Indicates that the timeline has been successfully copied. + summary: Copies timeline or timeline template + tags: + - Security Timeline API + - 'access:securitySolution' /api/timeline/_draft: get: operationId: GetDraftTimelines @@ -16406,23 +16411,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: Indicates that the draft timeline was successfully retrieved. '403': content: @@ -16482,23 +16472,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: >- Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft @@ -16651,28 +16626,14 @@ paths: schema: type: object properties: - file: - allOf: - - $ref: '#/components/schemas/Security_Timeline_API_Readable' - - type: object - properties: - hapi: - type: object - properties: - filename: - type: string - headers: - type: object - isImmutable: - enum: - - 'true' - - 'false' - type: string - required: - - filename - - headers - required: - - hapi + file: {} + isImmutable: + enum: + - 'true' + - 'false' + type: string + required: + - file description: The timelines to import as a readable stream. required: true responses: @@ -16680,13 +16641,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - $ref: >- - #/components/schemas/Security_Timeline_API_ImportTimelineResult - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_ImportTimelineResult description: Indicates the import of timelines was successful. '400': content: @@ -16744,7 +16700,9 @@ paths: properties: prepackagedTimelines: items: - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineSavedToReturnObject + nullable: true type: array timelinesToInstall: items: @@ -16767,13 +16725,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - $ref: >- - #/components/schemas/Security_Timeline_API_ImportTimelineResult - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_ImportTimelineResult description: Indicates the installation of prepackaged timelines was successful. '500': content: @@ -16811,19 +16764,16 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: + data: $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - nullable: true + #/components/schemas/Security_Timeline_API_ResolvedTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: The (template) timeline has been found '400': description: The request is missing parameters @@ -16891,36 +16841,26 @@ paths: schema: type: object properties: - data: - type: object - properties: - customTemplateTimelineCount: - type: number - defaultTimelineCount: - type: number - elasticTemplateTimelineCount: - type: number - favoriteCount: - type: number - templateTimelineCount: - type: number - timelines: - items: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - type: array - totalCount: - type: number - required: - - timelines - - totalCount - - defaultTimelineCount - - templateTimelineCount - - favoriteCount - - elasticTemplateTimelineCount - - customTemplateTimelineCount + customTemplateTimelineCount: + type: number + defaultTimelineCount: + type: number + elasticTemplateTimelineCount: + type: number + favoriteCount: + type: number + templateTimelineCount: + type: number + timeline: + items: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineResponse + type: array + totalCount: + type: number required: - - data + - timeline + - totalCount description: Indicates that the (template) timelines were found and returned. '400': content: @@ -31204,30 +31144,39 @@ components: type: object properties: aggregatable: + nullable: true type: boolean category: + nullable: true type: string columnHeaderType: + nullable: true type: string description: + nullable: true type: string example: - oneOf: - - type: string - - type: number + nullable: true + type: string id: + nullable: true type: string indexes: items: type: string + nullable: true type: array name: + nullable: true type: string placeholder: + nullable: true type: string searchable: + nullable: true type: boolean type: + nullable: true type: string Security_Timeline_API_DataProviderQueryMatch: type: object @@ -31249,6 +31198,10 @@ components: type: string queryMatch: $ref: '#/components/schemas/Security_Timeline_API_QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/Security_Timeline_API_DataProviderType' + nullable: true Security_Timeline_API_DataProviderResult: type: object properties: @@ -31336,41 +31289,59 @@ components: type: object properties: exists: - type: boolean + nullable: true + type: string match_all: + nullable: true type: string meta: + nullable: true type: object properties: alias: + nullable: true type: string controlledBy: + nullable: true type: string disabled: + nullable: true type: boolean field: + nullable: true type: string formattedValue: + nullable: true type: string index: + nullable: true type: string key: + nullable: true type: string negate: + nullable: true type: boolean params: + nullable: true type: string type: + nullable: true type: string value: + nullable: true type: string missing: + nullable: true type: string query: + nullable: true type: string range: + nullable: true type: string script: + nullable: true type: string Security_Timeline_API_GetNotesResult: type: object @@ -31435,6 +31406,12 @@ components: version: nullable: true type: string + required: + - savedObjectId + - version + - pinnedEventIds + - eventNotes + - globalNotes Security_Timeline_API_Note: allOf: - $ref: '#/components/schemas/Security_Timeline_API_BareNote' @@ -31455,6 +31432,23 @@ components: #/components/schemas/Security_Timeline_API_PinnedEventBaseResponseBody - nullable: true type: object + Security_Timeline_API_PersistTimelineResponse: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data Security_Timeline_API_PinnedEvent: allOf: - $ref: '#/components/schemas/Security_Timeline_API_BarePinnedEvent' @@ -31492,34 +31486,29 @@ components: nullable: true type: string value: - nullable: true - type: string - Security_Timeline_API_Readable: + oneOf: + - nullable: true + type: string + - items: + type: string + nullable: true + type: array + Security_Timeline_API_ResolvedTimeline: type: object properties: - _data: - additionalProperties: true - type: object - _encoding: + alias_purpose: + $ref: >- + #/components/schemas/Security_Timeline_API_SavedObjectResolveAliasPurpose + alias_target_id: type: string - _events: - additionalProperties: true - type: object - _eventsCount: - type: number - _maxListeners: - additionalProperties: true - type: object - _position: - type: number - _read: - additionalProperties: true - type: object - _readableState: - additionalProperties: true - type: object - readable: - type: boolean + outcome: + $ref: '#/components/schemas/Security_Timeline_API_SavedObjectResolveOutcome' + timeline: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineSavedToReturnObject + required: + - timeline + - outcome Security_Timeline_API_ResponseNote: type: object properties: @@ -31554,6 +31543,17 @@ components: - threat_match - zeek type: string + Security_Timeline_API_SavedObjectResolveAliasPurpose: + enum: + - savedObjectConversion + - savedObjectImport + type: string + Security_Timeline_API_SavedObjectResolveOutcome: + enum: + - exactMatch + - aliasMatch + - conflict + type: string Security_Timeline_API_SavedTimeline: type: object properties: @@ -31582,12 +31582,16 @@ components: properties: end: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number start: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number description: nullable: true type: string @@ -31677,6 +31681,18 @@ components: updatedBy: nullable: true type: string + Security_Timeline_API_SavedTimelineWithSavedObjectId: + allOf: + - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - type: object + properties: + savedObjectId: + type: string + version: + type: string + required: + - savedObjectId + - version Security_Timeline_API_SerializedFilterQueryResult: type: object properties: @@ -31726,27 +31742,64 @@ components: Security_Timeline_API_TimelineResponse: allOf: - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - $ref: >- + #/components/schemas/Security_Timeline_API_SavedTimelineWithSavedObjectId - type: object properties: eventIdToNoteIds: items: $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true type: array noteIds: items: type: string + nullable: true type: array notes: items: $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true type: array pinnedEventIds: items: type: string + nullable: true type: array pinnedEventsSaveObject: items: $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' + nullable: true + type: array + Security_Timeline_API_TimelineSavedToReturnObject: + allOf: + - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - type: object + properties: + eventIdToNoteIds: + items: + $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true + type: array + noteIds: + items: + type: string + nullable: true + type: array + notes: + items: + $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true + type: array + pinnedEventIds: + items: + type: string + nullable: true + type: array + pinnedEventsSaveObject: + items: + $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' + nullable: true type: array savedObjectId: type: string diff --git a/oas_docs/output/kibana.staging.yaml b/oas_docs/output/kibana.staging.yaml index 163791a7a27f4..930e27249f2c2 100644 --- a/oas_docs/output/kibana.staging.yaml +++ b/oas_docs/output/kibana.staging.yaml @@ -20322,19 +20322,21 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - nullable: true + data: + type: object + properties: + getOneTimeline: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineResponse + required: + - getOneTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: Indicates that the (template) timeline was found and returned. summary: >- Get an existing saved timeline or timeline template. This API is used to @@ -20373,23 +20375,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: >- Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft @@ -20450,21 +20437,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: Indicates the timeline was successfully created. '405': content: @@ -20481,6 +20455,37 @@ paths: tags: - Security Timeline API - 'access:securitySolution' + /api/timeline/_copy: + get: + description: | + Copies and returns a timeline or timeline template. + operationId: CopyTimeline + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + type: object + properties: + timeline: + $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + timelineIdToCopy: + type: string + required: + - timeline + - timelineIdToCopy + required: true + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse + description: Indicates that the timeline has been successfully copied. + summary: Copies timeline or timeline template + tags: + - Security Timeline API + - 'access:securitySolution' /api/timeline/_draft: get: operationId: GetDraftTimelines @@ -20495,23 +20500,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: Indicates that the draft timeline was successfully retrieved. '403': content: @@ -20571,23 +20561,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_PersistTimelineResponse description: >- Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft @@ -20740,28 +20715,14 @@ paths: schema: type: object properties: - file: - allOf: - - $ref: '#/components/schemas/Security_Timeline_API_Readable' - - type: object - properties: - hapi: - type: object - properties: - filename: - type: string - headers: - type: object - isImmutable: - enum: - - 'true' - - 'false' - type: string - required: - - filename - - headers - required: - - hapi + file: {} + isImmutable: + enum: + - 'true' + - 'false' + type: string + required: + - file description: The timelines to import as a readable stream. required: true responses: @@ -20769,13 +20730,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - $ref: >- - #/components/schemas/Security_Timeline_API_ImportTimelineResult - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_ImportTimelineResult description: Indicates the import of timelines was successful. '400': content: @@ -20833,7 +20789,9 @@ paths: properties: prepackagedTimelines: items: - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineSavedToReturnObject + nullable: true type: array timelinesToInstall: items: @@ -20856,13 +20814,8 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - $ref: >- - #/components/schemas/Security_Timeline_API_ImportTimelineResult - required: - - data + $ref: >- + #/components/schemas/Security_Timeline_API_ImportTimelineResult description: Indicates the installation of prepackaged timelines was successful. '500': content: @@ -20900,19 +20853,16 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: + data: $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - nullable: true + #/components/schemas/Security_Timeline_API_ResolvedTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: The (template) timeline has been found '400': description: The request is missing parameters @@ -20980,36 +20930,26 @@ paths: schema: type: object properties: - data: - type: object - properties: - customTemplateTimelineCount: - type: number - defaultTimelineCount: - type: number - elasticTemplateTimelineCount: - type: number - favoriteCount: - type: number - templateTimelineCount: - type: number - timelines: - items: - $ref: >- - #/components/schemas/Security_Timeline_API_TimelineResponse - type: array - totalCount: - type: number - required: - - timelines - - totalCount - - defaultTimelineCount - - templateTimelineCount - - favoriteCount - - elasticTemplateTimelineCount - - customTemplateTimelineCount + customTemplateTimelineCount: + type: number + defaultTimelineCount: + type: number + elasticTemplateTimelineCount: + type: number + favoriteCount: + type: number + templateTimelineCount: + type: number + timeline: + items: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineResponse + type: array + totalCount: + type: number required: - - data + - timeline + - totalCount description: Indicates that the (template) timelines were found and returned. '400': content: @@ -39213,30 +39153,39 @@ components: type: object properties: aggregatable: + nullable: true type: boolean category: + nullable: true type: string columnHeaderType: + nullable: true type: string description: + nullable: true type: string example: - oneOf: - - type: string - - type: number + nullable: true + type: string id: + nullable: true type: string indexes: items: type: string + nullable: true type: array name: + nullable: true type: string placeholder: + nullable: true type: string searchable: + nullable: true type: boolean type: + nullable: true type: string Security_Timeline_API_DataProviderQueryMatch: type: object @@ -39258,6 +39207,10 @@ components: type: string queryMatch: $ref: '#/components/schemas/Security_Timeline_API_QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/Security_Timeline_API_DataProviderType' + nullable: true Security_Timeline_API_DataProviderResult: type: object properties: @@ -39345,41 +39298,59 @@ components: type: object properties: exists: - type: boolean + nullable: true + type: string match_all: + nullable: true type: string meta: + nullable: true type: object properties: alias: + nullable: true type: string controlledBy: + nullable: true type: string disabled: + nullable: true type: boolean field: + nullable: true type: string formattedValue: + nullable: true type: string index: + nullable: true type: string key: + nullable: true type: string negate: + nullable: true type: boolean params: + nullable: true type: string type: + nullable: true type: string value: + nullable: true type: string missing: + nullable: true type: string query: + nullable: true type: string range: + nullable: true type: string script: + nullable: true type: string Security_Timeline_API_GetNotesResult: type: object @@ -39444,6 +39415,12 @@ components: version: nullable: true type: string + required: + - savedObjectId + - version + - pinnedEventIds + - eventNotes + - globalNotes Security_Timeline_API_Note: allOf: - $ref: '#/components/schemas/Security_Timeline_API_BareNote' @@ -39464,6 +39441,23 @@ components: #/components/schemas/Security_Timeline_API_PinnedEventBaseResponseBody - nullable: true type: object + Security_Timeline_API_PersistTimelineResponse: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data Security_Timeline_API_PinnedEvent: allOf: - $ref: '#/components/schemas/Security_Timeline_API_BarePinnedEvent' @@ -39501,34 +39495,29 @@ components: nullable: true type: string value: - nullable: true - type: string - Security_Timeline_API_Readable: + oneOf: + - nullable: true + type: string + - items: + type: string + nullable: true + type: array + Security_Timeline_API_ResolvedTimeline: type: object properties: - _data: - additionalProperties: true - type: object - _encoding: + alias_purpose: + $ref: >- + #/components/schemas/Security_Timeline_API_SavedObjectResolveAliasPurpose + alias_target_id: type: string - _events: - additionalProperties: true - type: object - _eventsCount: - type: number - _maxListeners: - additionalProperties: true - type: object - _position: - type: number - _read: - additionalProperties: true - type: object - _readableState: - additionalProperties: true - type: object - readable: - type: boolean + outcome: + $ref: '#/components/schemas/Security_Timeline_API_SavedObjectResolveOutcome' + timeline: + $ref: >- + #/components/schemas/Security_Timeline_API_TimelineSavedToReturnObject + required: + - timeline + - outcome Security_Timeline_API_ResponseNote: type: object properties: @@ -39563,6 +39552,17 @@ components: - threat_match - zeek type: string + Security_Timeline_API_SavedObjectResolveAliasPurpose: + enum: + - savedObjectConversion + - savedObjectImport + type: string + Security_Timeline_API_SavedObjectResolveOutcome: + enum: + - exactMatch + - aliasMatch + - conflict + type: string Security_Timeline_API_SavedTimeline: type: object properties: @@ -39591,12 +39591,16 @@ components: properties: end: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number start: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number description: nullable: true type: string @@ -39686,6 +39690,18 @@ components: updatedBy: nullable: true type: string + Security_Timeline_API_SavedTimelineWithSavedObjectId: + allOf: + - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - type: object + properties: + savedObjectId: + type: string + version: + type: string + required: + - savedObjectId + - version Security_Timeline_API_SerializedFilterQueryResult: type: object properties: @@ -39735,27 +39751,64 @@ components: Security_Timeline_API_TimelineResponse: allOf: - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - $ref: >- + #/components/schemas/Security_Timeline_API_SavedTimelineWithSavedObjectId - type: object properties: eventIdToNoteIds: items: $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true type: array noteIds: items: type: string + nullable: true type: array notes: items: $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true type: array pinnedEventIds: items: type: string + nullable: true type: array pinnedEventsSaveObject: items: $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' + nullable: true + type: array + Security_Timeline_API_TimelineSavedToReturnObject: + allOf: + - $ref: '#/components/schemas/Security_Timeline_API_SavedTimeline' + - type: object + properties: + eventIdToNoteIds: + items: + $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true + type: array + noteIds: + items: + type: string + nullable: true + type: array + notes: + items: + $ref: '#/components/schemas/Security_Timeline_API_Note' + nullable: true + type: array + pinnedEventIds: + items: + type: string + nullable: true + type: array + pinnedEventsSaveObject: + items: + $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' + nullable: true type: array savedObjectId: type: string diff --git a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts index d60eb93587ed8..8251f0e186779 100644 --- a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts +++ b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts @@ -296,6 +296,10 @@ import type { CleanDraftTimelinesRequestBodyInput, CleanDraftTimelinesResponse, } from './timeline/clean_draft_timelines/clean_draft_timelines_route.gen'; +import type { + CopyTimelineRequestBodyInput, + CopyTimelineResponse, +} from './timeline/copy_timeline/copy_timeline_route.gen'; import type { CreateTimelinesRequestBodyInput, CreateTimelinesResponse, @@ -551,6 +555,23 @@ after 30 days. It also deletes other artifacts specific to the migration impleme }) .catch(catchAxiosErrorFormatAndThrow); } + /** + * Copies and returns a timeline or timeline template. + + */ + async copyTimeline(props: CopyTimelineProps) { + this.log.info(`${new Date().toISOString()} Calling API CopyTimeline`); + return this.kbnClient + .request({ + path: '/api/timeline/_copy', + headers: { + [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31', + }, + method: 'GET', + body: props.body, + }) + .catch(catchAxiosErrorFormatAndThrow); + } async createAlertsIndex() { this.log.info(`${new Date().toISOString()} Calling API CreateAlertsIndex`); return this.kbnClient @@ -1946,6 +1967,9 @@ export interface BulkUpsertAssetCriticalityRecordsProps { export interface CleanDraftTimelinesProps { body: CleanDraftTimelinesRequestBodyInput; } +export interface CopyTimelineProps { + body: CopyTimelineRequestBodyInput; +} export interface CreateAlertsMigrationProps { body: CreateAlertsMigrationRequestBodyInput; } diff --git a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen.ts index 514a06ceec328..f3858b1cb8f34 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen.ts @@ -16,7 +16,7 @@ import { z } from '@kbn/zod'; -import { TimelineType, TimelineResponse } from '../model/components.gen'; +import { TimelineType, PersistTimelineResponse } from '../model/components.gen'; export type CleanDraftTimelinesRequestBody = z.infer; export const CleanDraftTimelinesRequestBody = z.object({ @@ -25,10 +25,4 @@ export const CleanDraftTimelinesRequestBody = z.object({ export type CleanDraftTimelinesRequestBodyInput = z.input; export type CleanDraftTimelinesResponse = z.infer; -export const CleanDraftTimelinesResponse = z.object({ - data: z.object({ - persistTimeline: z.object({ - timeline: TimelineResponse, - }), - }), -}); +export const CleanDraftTimelinesResponse = PersistTimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.schema.yaml index e5e9f3ed4cfc6..7df51d4ff883d 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Draft Timeline API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_draft: post: @@ -37,19 +30,7 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [persistTimeline] - properties: - persistTimeline: - type: object - required: [timeline] - properties: - timeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + $ref: '../model/components.schema.yaml#/components/schemas/PersistTimelineResponse' '403': description: Indicates that the user does not have the required permissions to create a draft timeline. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.gen.ts new file mode 100644 index 0000000000000..6e75f0baaf7ef --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.gen.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Copy Timeline API + * version: 2023-10-31 + */ + +import { z } from '@kbn/zod'; + +import { SavedTimeline, PersistTimelineResponse } from '../model/components.gen'; + +export type CopyTimelineRequestBody = z.infer; +export const CopyTimelineRequestBody = z.object({ + timeline: SavedTimeline, + timelineIdToCopy: z.string(), +}); +export type CopyTimelineRequestBodyInput = z.input; + +export type CopyTimelineResponse = z.infer; +export const CopyTimelineResponse = PersistTimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.schema.yaml new file mode 100644 index 0000000000000..743de4c334fe5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.schema.yaml @@ -0,0 +1,34 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Copy Timeline API + version: '2023-10-31' +paths: + /api/timeline/_copy: + get: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: CopyTimeline + summary: Copies timeline or timeline template + description: | + Copies and returns a timeline or timeline template. + tags: + - access:securitySolution + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [timeline, timelineIdToCopy] + properties: + timeline: + $ref: '../model/components.schema.yaml#/components/schemas/SavedTimeline' + timelineIdToCopy: + type: string + responses: + '200': + description: Indicates that the timeline has been successfully copied. + content: + application/json: + schema: + $ref: '../model/components.schema.yaml#/components/schemas/PersistTimelineResponse' diff --git a/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.ts b/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.ts deleted file mode 100644 index 1b7dc1d4c3566..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/copy_timeline/copy_timeline_route.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 * as rt from 'io-ts'; - -import { SavedTimelineRuntimeType } from '../model/api'; - -export const copyTimelineSchema = rt.type({ - timeline: SavedTimelineRuntimeType, - timelineIdToCopy: rt.string, -}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.gen.ts index 51af791fbc3f0..33fc1855f390a 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.gen.ts @@ -17,29 +17,23 @@ import { z } from '@kbn/zod'; import { + SavedTimeline, TimelineStatus, TimelineType, - SavedTimeline, - TimelineResponse, + PersistTimelineResponse, } from '../model/components.gen'; export type CreateTimelinesRequestBody = z.infer; export const CreateTimelinesRequestBody = z.object({ + timeline: SavedTimeline, status: TimelineStatus.nullable().optional(), timelineId: z.string().nullable().optional(), templateTimelineId: z.string().nullable().optional(), templateTimelineVersion: z.number().nullable().optional(), timelineType: TimelineType.nullable().optional(), version: z.string().nullable().optional(), - timeline: SavedTimeline, }); export type CreateTimelinesRequestBodyInput = z.input; export type CreateTimelinesResponse = z.infer; -export const CreateTimelinesResponse = z.object({ - data: z.object({ - persistTimeline: z.object({ - timeline: TimelineResponse.optional(), - }), - }), -}); +export const CreateTimelinesResponse = PersistTimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.schema.yaml index 561ce75c84fe2..77296f11188e5 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-create.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline: post: @@ -28,9 +21,10 @@ paths: application/json: schema: type: object - required: - - timeline + required: [timeline] properties: + timeline: + $ref: '../model/components.schema.yaml#/components/schemas/SavedTimeline' status: $ref: '../model/components.schema.yaml#/components/schemas/TimelineStatus' nullable: true @@ -49,26 +43,13 @@ paths: version: type: string nullable: true - timeline: - $ref: '../model/components.schema.yaml#/components/schemas/SavedTimeline' responses: '200': description: Indicates the timeline was successfully created. content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [persistTimeline] - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + $ref: '../model/components.schema.yaml#/components/schemas/PersistTimelineResponse' '405': description: Indicates that there was an error in the timeline creation. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.ts b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.ts deleted file mode 100644 index 1911df3941a35..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 * as rt from 'io-ts'; - -import type { ResponseTimeline } from '../model/api'; -import { - SavedTimelineRuntimeType, - TimelineStatusLiteralRt, - TimelineTypeLiteralRt, -} from '../model/api'; -import { unionWithNullType } from '../../../utility_types'; - -export const createTimelineSchema = rt.intersection([ - rt.type({ - timeline: SavedTimelineRuntimeType, - }), - rt.partial({ - status: unionWithNullType(TimelineStatusLiteralRt), - timelineId: unionWithNullType(rt.string), - templateTimelineId: unionWithNullType(rt.string), - templateTimelineVersion: unionWithNullType(rt.number), - timelineType: unionWithNullType(TimelineTypeLiteralRt), - version: unionWithNullType(rt.string), - }), -]); - -export interface CreateTimelinesResponse { - data: { - persistTimeline: ResponseTimeline; - }; -} diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml index 2fb9ecd320094..cf0dcf27fc309 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Notes API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/note: delete: diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml index 85c1b7c9a6736..607503238eea1 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-delete.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline: delete: diff --git a/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.schema.yaml index 2c0a367dc5d28..64ada73260ed1 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-import.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_export: post: diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen.ts index edbbc37744d03..327f02a8fb6f5 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen.ts @@ -16,7 +16,7 @@ import { z } from '@kbn/zod'; -import { TimelineType, TimelineResponse } from '../model/components.gen'; +import { TimelineType, PersistTimelineResponse } from '../model/components.gen'; export type GetDraftTimelinesRequestQuery = z.infer; export const GetDraftTimelinesRequestQuery = z.object({ @@ -25,10 +25,4 @@ export const GetDraftTimelinesRequestQuery = z.object({ export type GetDraftTimelinesRequestQueryInput = z.input; export type GetDraftTimelinesResponse = z.infer; -export const GetDraftTimelinesResponse = z.object({ - data: z.object({ - persistTimeline: z.object({ - timeline: TimelineResponse, - }), - }), -}); +export const GetDraftTimelinesResponse = PersistTimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.schema.yaml index c7a77af98a7f3..79082e20b32da 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Get Draft Timelines API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_draft: get: @@ -30,19 +23,7 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [persistTimeline] - properties: - persistTimeline: - type: object - required: [timeline] - properties: - timeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + $ref: '../model/components.schema.yaml#/components/schemas/PersistTimelineResponse' '403': description: If a draft timeline was not found and we attempted to create one, it indicates that the user does not have the required permissions to create a draft timeline. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.ts b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.ts deleted file mode 100644 index 13625715289f6..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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 * as rt from 'io-ts'; - -import { TimelineTypeLiteralRt } from '../model/api'; - -export const getDraftTimelineSchema = rt.type({ - timelineType: TimelineTypeLiteralRt, -}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml index ebf517c447aa9..0f4aa62178b6c 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Notes API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/note: get: diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts index 77c8edb5c69b0..fe64508c28c71 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts @@ -32,8 +32,11 @@ export const GetTimelineRequestQuery = z.object({ export type GetTimelineRequestQueryInput = z.input; export type GetTimelineResponse = z.infer; -export const GetTimelineResponse = z.object({ - data: z.object({ - getOneTimeline: TimelineResponse.nullable(), +export const GetTimelineResponse = z.union([ + z.object({ + data: z.object({ + getOneTimeline: TimelineResponse, + }), }), -}); + z.object({}).strict(), +]); diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml index 9a94bafb63a76..1c288ff443b16 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/_get_timeline_or_timeline_template_by_savedobjectid.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline: get: @@ -38,13 +31,15 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [getOneTimeline] + oneOf: + - type: object + required: [data] properties: - getOneTimeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' - nullable: true + data: + type: object + required: [getOneTimeline] + properties: + getOneTimeline: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + - type: object + additionalProperties: false diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.ts b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.ts deleted file mode 100644 index cca6886f42025..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 * as rt from 'io-ts'; - -export const getTimelineQuerySchema = rt.partial({ - template_timeline_id: rt.string, - id: rt.string, -}); - -export type GetTimelineQuery = rt.TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.gen.ts index 5ce3256ae48be..714746875599b 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.gen.ts @@ -41,13 +41,11 @@ export type GetTimelinesRequestQueryInput = z.input; export const GetTimelinesResponse = z.object({ - data: z.object({ - timelines: z.array(TimelineResponse), - totalCount: z.number(), - defaultTimelineCount: z.number(), - templateTimelineCount: z.number(), - favoriteCount: z.number(), - elasticTemplateTimelineCount: z.number(), - customTemplateTimelineCount: z.number(), - }), + timeline: z.array(TimelineResponse), + totalCount: z.number(), + defaultTimelineCount: z.number().optional(), + templateTimelineCount: z.number().optional(), + favoriteCount: z.number().optional(), + elasticTemplateTimelineCount: z.number().optional(), + customTemplateTimelineCount: z.number().optional(), }); diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.schema.yaml index 189865a9344d4..e8fc107c8f7e0 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-get.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timelines: get: @@ -74,37 +67,27 @@ paths: application/json: schema: type: object - required: [data] + required: [ + timeline, + totalCount, + ] properties: - data: - type: object - required: - [ - timelines, - totalCount, - defaultTimelineCount, - templateTimelineCount, - favoriteCount, - elasticTemplateTimelineCount, - customTemplateTimelineCount, - ] - properties: - timelines: - type: array - items: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' - totalCount: - type: number - defaultTimelineCount: - type: number - templateTimelineCount: - type: number - favoriteCount: - type: number - elasticTemplateTimelineCount: - type: number - customTemplateTimelineCount: - type: number + timeline: + type: array + items: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + totalCount: + type: number + defaultTimelineCount: + type: number + templateTimelineCount: + type: number + favoriteCount: + type: number + elasticTemplateTimelineCount: + type: number + customTemplateTimelineCount: + type: number '400': description: Bad request. The user supplied invalid data. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.ts b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.ts deleted file mode 100644 index 9f35197358aea..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 * as rt from 'io-ts'; -import { - direction, - sortFieldTimeline, - TimelineStatusLiteralRt, - TimelineTypeLiteralRt, -} from '../model/api'; -import { unionWithNullType } from '../../../utility_types'; - -const BoolFromString = rt.union([rt.literal('true'), rt.literal('false')]); - -export const getTimelinesQuerySchema = rt.partial({ - only_user_favorite: unionWithNullType(BoolFromString), - page_index: unionWithNullType(rt.string), - page_size: unionWithNullType(rt.string), - search: unionWithNullType(rt.string), - sort_field: sortFieldTimeline, - sort_order: direction, - status: unionWithNullType(TimelineStatusLiteralRt), - timeline_type: unionWithNullType(TimelineTypeLiteralRt), -}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.gen.ts index 4624ffd713430..660f8bc287665 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.gen.ts @@ -16,23 +16,14 @@ import { z } from '@kbn/zod'; -import { Readable, ImportTimelineResult } from '../model/components.gen'; +import { ImportTimelineResult } from '../model/components.gen'; export type ImportTimelinesRequestBody = z.infer; export const ImportTimelinesRequestBody = z.object({ - file: Readable.merge( - z.object({ - hapi: z.object({ - filename: z.string(), - headers: z.object({}), - isImmutable: z.enum(['true', 'false']).optional(), - }), - }) - ), + isImmutable: z.enum(['true', 'false']).optional(), + file: z.unknown(), }); export type ImportTimelinesRequestBodyInput = z.input; export type ImportTimelinesResponse = z.infer; -export const ImportTimelinesResponse = z.object({ - data: ImportTimelineResult, -}); +export const ImportTimelinesResponse = ImportTimelineResult; diff --git a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.schema.yaml index 9925ae10322e6..66dba9bb7e5f1 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.schema.yaml @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-import.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_import: post: @@ -28,37 +21,21 @@ paths: application/json: schema: type: object + required: [file] properties: - file: - allOf: - - $ref: '../model/components.schema.yaml#/components/schemas/Readable' - - type: object - required: [hapi] - properties: - hapi: - type: object - required: [filename, headers] - properties: - filename: - type: string - headers: - type: object - isImmutable: - type: string - enum: - - 'true' - - 'false' + isImmutable: + type: string + enum: + - 'true' + - 'false' + file: {} responses: '200': description: Indicates the import of timelines was successful. content: application/json: schema: - type: object - required: [data] - properties: - data: - $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelineResult' + $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelineResult' '400': description: Indicates the import of timelines was unsuccessful because of an invalid file extension. diff --git a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.ts b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.ts deleted file mode 100644 index 2ad6f3f8c7333..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 * as rt from 'io-ts'; - -import { BareNoteSchema, SavedTimelineRuntimeType } from '../model/api'; -import { unionWithNullType } from '../../../utility_types'; - -const pinnedEventIds = unionWithNullType(rt.array(rt.string)); - -export const eventNotes = unionWithNullType(rt.array(BareNoteSchema)); -export const globalNotes = unionWithNullType(rt.array(BareNoteSchema)); - -export const ImportTimelinesSchemaRt = rt.intersection([ - SavedTimelineRuntimeType, - rt.type({ - savedObjectId: unionWithNullType(rt.string), - version: unionWithNullType(rt.string), - }), - rt.type({ - globalNotes, - eventNotes, - pinnedEventIds, - }), -]); - -export type ImportTimelinesSchema = rt.TypeOf; - -const ReadableRt = rt.partial({ - _maxListeners: rt.unknown, - _readableState: rt.unknown, - _read: rt.unknown, - readable: rt.boolean, - _events: rt.unknown, - _eventsCount: rt.number, - _data: rt.unknown, - _position: rt.number, - _encoding: rt.string, -}); - -const booleanInString = rt.union([rt.literal('true'), rt.literal('false')]); - -export const ImportTimelinesPayloadSchemaRt = rt.intersection([ - rt.type({ - file: rt.intersection([ - ReadableRt, - rt.type({ - hapi: rt.type({ - filename: rt.string, - headers: rt.unknown, - }), - }), - ]), - }), - rt.partial({ isImmutable: booleanInString }), -]); diff --git a/x-pack/plugins/security_solution/common/api/timeline/index.ts b/x-pack/plugins/security_solution/common/api/timeline/index.ts index 806c0c8539d97..0a148bcc59797 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/index.ts @@ -7,13 +7,3 @@ export * from './model/api'; export * from './routes'; - -export * from './get_draft_timelines/get_draft_timelines_route'; -export * from './create_timelines/create_timelines_route'; -export * from './get_timeline/get_timeline_route'; -export * from './get_timelines/get_timelines_route'; -export * from './import_timelines/import_timelines_route'; -export * from './patch_timelines/patch_timelines_schema'; -export * from './pinned_events/pinned_events_route'; -export * from './install_prepackaged_timelines/install_prepackaged_timelines'; -export * from './copy_timeline/copy_timeline_route'; diff --git a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines.ts b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines.ts deleted file mode 100644 index 0d9b2d3e81121..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 * as rt from 'io-ts'; - -import { unionWithNullType } from '../../../utility_types'; -import { ImportTimelinesSchemaRt, TimelineSavedToReturnObjectRuntimeType } from '..'; - -export const checkTimelineStatusRt = rt.type({ - timelinesToInstall: rt.array(unionWithNullType(ImportTimelinesSchemaRt)), - timelinesToUpdate: rt.array(unionWithNullType(ImportTimelinesSchemaRt)), - prepackagedTimelines: rt.array(unionWithNullType(TimelineSavedToReturnObjectRuntimeType)), -}); - -export type CheckTimelineStatusRt = rt.TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen.ts index add1e2ffa94b8..f2f09c0257e87 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen.ts @@ -16,7 +16,11 @@ import { z } from '@kbn/zod'; -import { ImportTimelines, SavedTimeline, ImportTimelineResult } from '../model/components.gen'; +import { + ImportTimelines, + TimelineSavedToReturnObject, + ImportTimelineResult, +} from '../model/components.gen'; export type InstallPrepackedTimelinesRequestBody = z.infer< typeof InstallPrepackedTimelinesRequestBody @@ -24,13 +28,11 @@ export type InstallPrepackedTimelinesRequestBody = z.infer< export const InstallPrepackedTimelinesRequestBody = z.object({ timelinesToInstall: z.array(ImportTimelines.nullable()), timelinesToUpdate: z.array(ImportTimelines.nullable()), - prepackagedTimelines: z.array(SavedTimeline), + prepackagedTimelines: z.array(TimelineSavedToReturnObject.nullable()), }); export type InstallPrepackedTimelinesRequestBodyInput = z.input< typeof InstallPrepackedTimelinesRequestBody >; export type InstallPrepackedTimelinesResponse = z.infer; -export const InstallPrepackedTimelinesResponse = z.object({ - data: ImportTimelineResult, -}); +export const InstallPrepackedTimelinesResponse = ImportTimelineResult; diff --git a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.schema.yaml index af96bbeb9c6d2..db6123b0168a2 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Install Prepackaged Timelines API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_prepackaged: post: @@ -40,18 +33,15 @@ paths: prepackagedTimelines: type: array items: - $ref: '../model/components.schema.yaml#/components/schemas/SavedTimeline' + $ref: '../model/components.schema.yaml#/components/schemas/TimelineSavedToReturnObject' + nullable: true responses: '200': description: Indicates the installation of prepackaged timelines was successful. content: application/json: schema: - type: object - required: [data] - properties: - data: - $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelineResult' + $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelineResult' '500': description: Indicates the installation of prepackaged timelines was unsuccessful. content: 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 ff6707b700626..250daaf3077eb 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 @@ -5,30 +5,35 @@ * 2.0. */ -import * as runtimeTypes from 'io-ts'; -import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; - -import { stringEnum, unionWithNullType } from '../../../utility_types'; - -import type { Maybe } from '../../../search_strategy'; -import { Direction } from '../../../search_strategy'; -import { PinnedEventRuntimeType } from '../pinned_events/pinned_events_route'; -import { ErrorSchema } from './error_schema'; import type { DataProviderType } from './components.gen'; import { BareNote, BarePinnedEvent, + ColumnHeaderResult, DataProviderTypeEnum, + DataProviderResult, FavoriteTimelineResponse, - type FavoriteTimelineResult, + FilterTimelineResult, + ImportTimelineResult, + ImportTimelines, type Note, PinnedEvent, + PersistTimelineResponse, + QueryMatchResult, + ResolvedTimeline, RowRendererId, RowRendererIdEnum, + SavedTimeline, + SavedTimelineWithSavedObjectId, + Sort, + SortDirection, SortFieldTimeline, SortFieldTimelineEnum, TemplateTimelineType, TemplateTimelineTypeEnum, + TimelineErrorResponse, + TimelineResponse, + TimelineSavedToReturnObject, TimelineStatus, TimelineStatusEnum, TimelineType, @@ -38,62 +43,45 @@ import { export { BareNote, BarePinnedEvent, + ColumnHeaderResult, + DataProviderResult, DataProviderType, DataProviderTypeEnum, FavoriteTimelineResponse, + FilterTimelineResult, + ImportTimelineResult, + ImportTimelines, Note, PinnedEvent, + PersistTimelineResponse, + QueryMatchResult, + ResolvedTimeline, RowRendererId, RowRendererIdEnum, + SavedTimeline, + SavedTimelineWithSavedObjectId, + Sort, + SortDirection, SortFieldTimeline, SortFieldTimelineEnum, TemplateTimelineType, + TimelineErrorResponse, + TimelineResponse, TemplateTimelineTypeEnum, + TimelineSavedToReturnObject, TimelineStatus, TimelineStatusEnum, TimelineType, TimelineTypeEnum, }; -export type BarePinnedEventWithoutExternalRefs = Omit; - /** - * Outcome is a property of the saved object resolve api - * will tell us info about the rule after 8.0 migrations + * This type represents a timeline type stored in a saved object that does not include any fields that reference + * other saved objects. */ -export type SavedObjectResolveOutcome = runtimeTypes.TypeOf; -export const SavedObjectResolveOutcome = runtimeTypes.union([ - runtimeTypes.literal('exactMatch'), - runtimeTypes.literal('aliasMatch'), - runtimeTypes.literal('conflict'), -]); - -export type SavedObjectResolveAliasTargetId = runtimeTypes.TypeOf< - typeof SavedObjectResolveAliasTargetId ->; -export const SavedObjectResolveAliasTargetId = runtimeTypes.string; - -export type SavedObjectResolveAliasPurpose = runtimeTypes.TypeOf< - typeof SavedObjectResolveAliasPurpose ->; -export const SavedObjectResolveAliasPurpose = runtimeTypes.union([ - runtimeTypes.literal('savedObjectConversion'), - runtimeTypes.literal('savedObjectImport'), -]); +export type TimelineWithoutExternalRefs = Omit; -export const BareNoteSchema = runtimeTypes.intersection([ - runtimeTypes.type({ - timelineId: runtimeTypes.string, - }), - runtimeTypes.partial({ - eventId: unionWithNullType(runtimeTypes.string), - note: unionWithNullType(runtimeTypes.string), - created: unionWithNullType(runtimeTypes.number), - createdBy: unionWithNullType(runtimeTypes.string), - updated: unionWithNullType(runtimeTypes.number), - updatedBy: unionWithNullType(runtimeTypes.string), - }), -]); +export type BarePinnedEventWithoutExternalRefs = Omit; /** * This type represents a note type stored in a saved object that does not include any fields that reference @@ -101,350 +89,9 @@ export const BareNoteSchema = runtimeTypes.intersection([ */ export type BareNoteWithoutExternalRefs = Omit; -export const NoteRuntimeType = runtimeTypes.intersection([ - BareNoteSchema, - runtimeTypes.type({ - noteId: runtimeTypes.string, - version: runtimeTypes.string, - }), -]); - -/* - * ColumnHeader Types - */ -const SavedColumnHeaderRuntimeType = runtimeTypes.partial({ - aggregatable: unionWithNullType(runtimeTypes.boolean), - category: unionWithNullType(runtimeTypes.string), - columnHeaderType: unionWithNullType(runtimeTypes.string), - description: unionWithNullType(runtimeTypes.string), - example: unionWithNullType(runtimeTypes.string), - indexes: unionWithNullType(runtimeTypes.array(runtimeTypes.string)), - id: unionWithNullType(runtimeTypes.string), - name: unionWithNullType(runtimeTypes.string), - placeholder: unionWithNullType(runtimeTypes.string), - searchable: unionWithNullType(runtimeTypes.boolean), - type: unionWithNullType(runtimeTypes.string), -}); - -/* - * DataProvider Types - */ -const SavedDataProviderQueryMatchBasicRuntimeType = runtimeTypes.partial({ - field: unionWithNullType(runtimeTypes.string), - displayField: unionWithNullType(runtimeTypes.string), - value: runtimeTypes.union([ - runtimeTypes.null, - runtimeTypes.string, - runtimeTypes.array(runtimeTypes.string), - ]), - displayValue: unionWithNullType(runtimeTypes.string), - operator: unionWithNullType(runtimeTypes.string), -}); - -const SavedDataProviderQueryMatchRuntimeType = runtimeTypes.partial({ - id: unionWithNullType(runtimeTypes.string), - name: unionWithNullType(runtimeTypes.string), - enabled: unionWithNullType(runtimeTypes.boolean), - excluded: unionWithNullType(runtimeTypes.boolean), - kqlQuery: unionWithNullType(runtimeTypes.string), - queryMatch: unionWithNullType(SavedDataProviderQueryMatchBasicRuntimeType), -}); - -export const DataProviderTypeLiteralRt = runtimeTypes.union([ - runtimeTypes.literal(DataProviderTypeEnum.default), - runtimeTypes.literal(DataProviderTypeEnum.template), -]); - -const SavedDataProviderRuntimeType = runtimeTypes.partial({ - id: unionWithNullType(runtimeTypes.string), - name: unionWithNullType(runtimeTypes.string), - enabled: unionWithNullType(runtimeTypes.boolean), - excluded: unionWithNullType(runtimeTypes.boolean), - kqlQuery: unionWithNullType(runtimeTypes.string), - queryMatch: unionWithNullType(SavedDataProviderQueryMatchBasicRuntimeType), - and: unionWithNullType(runtimeTypes.array(SavedDataProviderQueryMatchRuntimeType)), - type: unionWithNullType(DataProviderTypeLiteralRt), -}); - -/* - * Filters Types - */ -const SavedFilterMetaRuntimeType = runtimeTypes.partial({ - alias: unionWithNullType(runtimeTypes.string), - controlledBy: unionWithNullType(runtimeTypes.string), - disabled: unionWithNullType(runtimeTypes.boolean), - field: unionWithNullType(runtimeTypes.string), - formattedValue: unionWithNullType(runtimeTypes.string), - index: unionWithNullType(runtimeTypes.string), - key: unionWithNullType(runtimeTypes.string), - negate: unionWithNullType(runtimeTypes.boolean), - params: unionWithNullType(runtimeTypes.string), - type: unionWithNullType(runtimeTypes.string), - value: unionWithNullType(runtimeTypes.string), -}); - -const SavedFilterRuntimeType = runtimeTypes.partial({ - exists: unionWithNullType(runtimeTypes.string), - meta: unionWithNullType(SavedFilterMetaRuntimeType), - match_all: unionWithNullType(runtimeTypes.string), - missing: unionWithNullType(runtimeTypes.string), - query: unionWithNullType(runtimeTypes.string), - range: unionWithNullType(runtimeTypes.string), - script: unionWithNullType(runtimeTypes.string), -}); - -/* - * eqlOptionsQuery -> filterQuery Types - */ -const EqlOptionsRuntimeType = runtimeTypes.partial({ - eventCategoryField: unionWithNullType(runtimeTypes.string), - query: unionWithNullType(runtimeTypes.string), - tiebreakerField: unionWithNullType(runtimeTypes.string), - timestampField: unionWithNullType(runtimeTypes.string), - size: unionWithNullType(runtimeTypes.union([runtimeTypes.string, runtimeTypes.number])), -}); - -/* - * kqlQuery -> filterQuery Types - */ -const SavedKueryFilterQueryRuntimeType = runtimeTypes.partial({ - kind: unionWithNullType(runtimeTypes.string), - expression: unionWithNullType(runtimeTypes.string), -}); - -const SavedSerializedFilterQueryQueryRuntimeType = runtimeTypes.partial({ - kuery: unionWithNullType(SavedKueryFilterQueryRuntimeType), - serializedQuery: unionWithNullType(runtimeTypes.string), -}); - -const SavedFilterQueryQueryRuntimeType = runtimeTypes.partial({ - filterQuery: unionWithNullType(SavedSerializedFilterQueryQueryRuntimeType), -}); - -/* - * DatePicker Range Types - */ -const SavedDateRangePickerRuntimeType = runtimeTypes.partial({ - /* Before the change of all timestamp to ISO string the values of start and from - * attributes where a number. Specifically UNIX timestamps. - * To support old timeline's saved object we need to add the number io-ts type - */ - start: unionWithNullType(runtimeTypes.union([runtimeTypes.string, runtimeTypes.number])), - end: unionWithNullType(runtimeTypes.union([runtimeTypes.string, runtimeTypes.number])), -}); - -/* - * Favorite Types - */ -const SavedFavoriteRuntimeType = runtimeTypes.partial({ - keySearch: unionWithNullType(runtimeTypes.string), - favoriteDate: unionWithNullType(runtimeTypes.number), - fullName: unionWithNullType(runtimeTypes.string), - userName: unionWithNullType(runtimeTypes.string), -}); - -/* - * Sort Types - */ - -const SavedSortObject = runtimeTypes.partial({ - columnId: unionWithNullType(runtimeTypes.string), - columnType: unionWithNullType(runtimeTypes.string), - sortDirection: unionWithNullType(runtimeTypes.string), -}); -const SavedSortRuntimeType = runtimeTypes.union([ - runtimeTypes.array(SavedSortObject), - SavedSortObject, -]); - -export type Sort = runtimeTypes.TypeOf; - -/* - * Timeline Statuses - */ - -export const TimelineStatusLiteralRt = runtimeTypes.union([ - runtimeTypes.literal(TimelineStatusEnum.active), - runtimeTypes.literal(TimelineStatusEnum.draft), - runtimeTypes.literal(TimelineStatusEnum.immutable), -]); - export const RowRendererCount = Object.keys(RowRendererIdEnum).length; export const RowRendererValues = Object.values(RowRendererId.Values); -const RowRendererIdRuntimeType = stringEnum(RowRendererIdEnum, 'RowRendererId'); - -/** - * Timeline types - */ - -export const TimelineTypeLiteralRt = runtimeTypes.union([ - runtimeTypes.literal(TimelineTypeEnum.template), - runtimeTypes.literal(TimelineTypeEnum.default), -]); - -/** - * This is the response type - */ -export const SavedTimelineRuntimeType = runtimeTypes.partial({ - columns: unionWithNullType(runtimeTypes.array(SavedColumnHeaderRuntimeType)), - dataProviders: unionWithNullType(runtimeTypes.array(SavedDataProviderRuntimeType)), - dataViewId: unionWithNullType(runtimeTypes.string), - description: unionWithNullType(runtimeTypes.string), - eqlOptions: unionWithNullType(EqlOptionsRuntimeType), - eventType: unionWithNullType(runtimeTypes.string), - excludedRowRendererIds: unionWithNullType(runtimeTypes.array(RowRendererIdRuntimeType)), - favorite: unionWithNullType(runtimeTypes.array(SavedFavoriteRuntimeType)), - filters: unionWithNullType(runtimeTypes.array(SavedFilterRuntimeType)), - indexNames: unionWithNullType(runtimeTypes.array(runtimeTypes.string)), - 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), - status: unionWithNullType(TimelineStatusLiteralRt), - created: unionWithNullType(runtimeTypes.number), - createdBy: unionWithNullType(runtimeTypes.string), - updated: unionWithNullType(runtimeTypes.number), - updatedBy: unionWithNullType(runtimeTypes.string), - savedSearchId: unionWithNullType(runtimeTypes.string), -}); - -export type SavedTimeline = runtimeTypes.TypeOf; - -export type SavedTimelineWithSavedObjectId = SavedTimeline & { - savedObjectId?: string | null; -}; - -/** - * This type represents a timeline type stored in a saved object that does not include any fields that reference - * other saved objects. - */ -export type TimelineWithoutExternalRefs = Omit; - -export const TimelineSavedToReturnObjectRuntimeType = runtimeTypes.intersection([ - SavedTimelineRuntimeType, - runtimeTypes.type({ - savedObjectId: runtimeTypes.string, - version: runtimeTypes.string, - }), - runtimeTypes.partial({ - eventIdToNoteIds: runtimeTypes.array(NoteRuntimeType), - noteIds: runtimeTypes.array(runtimeTypes.string), - notes: runtimeTypes.array(NoteRuntimeType), - pinnedEventIds: runtimeTypes.array(runtimeTypes.string), - pinnedEventsSaveObject: runtimeTypes.array(PinnedEventRuntimeType), - }), -]); - -export type TimelineSavedObject = runtimeTypes.TypeOf< - typeof TimelineSavedToReturnObjectRuntimeType ->; - -export const SingleTimelineResponseType = runtimeTypes.type({ - data: runtimeTypes.type({ - getOneTimeline: TimelineSavedToReturnObjectRuntimeType, - }), -}); - -export type SingleTimelineResponse = runtimeTypes.TypeOf; - -/** Resolved Timeline Response */ -export const ResolvedTimelineSavedObjectToReturnObjectRuntimeType = runtimeTypes.intersection([ - runtimeTypes.type({ - timeline: TimelineSavedToReturnObjectRuntimeType, - outcome: SavedObjectResolveOutcome, - }), - runtimeTypes.partial({ - alias_target_id: SavedObjectResolveAliasTargetId, - alias_purpose: SavedObjectResolveAliasPurpose, - }), -]); - -export type ResolvedTimelineWithOutcomeSavedObject = runtimeTypes.TypeOf< - typeof ResolvedTimelineSavedObjectToReturnObjectRuntimeType ->; - -export const ResolvedSingleTimelineResponseType = runtimeTypes.type({ - data: ResolvedTimelineSavedObjectToReturnObjectRuntimeType, -}); - -export type SingleTimelineResolveResponse = runtimeTypes.TypeOf< - typeof ResolvedSingleTimelineResponseType ->; - -const responseTimelines = runtimeTypes.type({ - timeline: runtimeTypes.array(TimelineSavedToReturnObjectRuntimeType), - totalCount: runtimeTypes.number, -}); - -export type ResponseTimelines = runtimeTypes.TypeOf; - -export const allTimelinesResponse = runtimeTypes.intersection([ - responseTimelines, - runtimeTypes.type({ - defaultTimelineCount: runtimeTypes.number, - templateTimelineCount: runtimeTypes.number, - elasticTemplateTimelineCount: runtimeTypes.number, - customTemplateTimelineCount: runtimeTypes.number, - favoriteCount: runtimeTypes.number, - }), -]); - -export type AllTimelinesResponse = runtimeTypes.TypeOf; - -/** - * 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 const TimelineErrorResponseType = runtimeTypes.union([ - runtimeTypes.type({ - status_code: runtimeTypes.number, - message: runtimeTypes.string, - }), - runtimeTypes.type({ - statusCode: runtimeTypes.number, - message: runtimeTypes.string, - }), -]); - -export type TimelineErrorResponse = runtimeTypes.TypeOf; -export type TimelineResponse = runtimeTypes.TypeOf; - -export const sortFieldTimeline = runtimeTypes.union([ - runtimeTypes.literal(SortFieldTimelineEnum.title), - runtimeTypes.literal(SortFieldTimelineEnum.description), - runtimeTypes.literal(SortFieldTimelineEnum.updated), - runtimeTypes.literal(SortFieldTimelineEnum.created), -]); - -export const direction = runtimeTypes.union([ - runtimeTypes.literal(Direction.asc), - runtimeTypes.literal(Direction.desc), -]); - -export const sortTimeline = runtimeTypes.type({ - sortField: sortFieldTimeline, - sortOrder: direction, -}); - /** * Import/export timelines */ @@ -457,187 +104,17 @@ export interface ExportedNotes { globalNotes: ExportedGlobalNotes; } -export type ExportedTimelines = SavedTimeline & - ExportedNotes & { - pinnedEventIds: string[]; - }; - export interface ExportTimelineNotFoundError { statusCode: number; message: string; } -export const importTimelineResultSchema = runtimeTypes.exact( - runtimeTypes.type({ - success: runtimeTypes.boolean, - success_count: PositiveInteger, - timelines_installed: PositiveInteger, - timelines_updated: PositiveInteger, - errors: runtimeTypes.array(ErrorSchema), - }) -); - -export type ImportTimelineResultSchema = runtimeTypes.TypeOf; - -export const pageInfoTimeline = runtimeTypes.type({ - pageIndex: runtimeTypes.number, - pageSize: runtimeTypes.number, -}); - export interface PageInfoTimeline { pageIndex: number; pageSize: number; } -export const getTimelinesArgs = runtimeTypes.partial({ - onlyUserFavorite: unionWithNullType(runtimeTypes.boolean), - pageInfo: unionWithNullType(pageInfoTimeline), - search: unionWithNullType(runtimeTypes.string), - sort: unionWithNullType(sortTimeline), - status: unionWithNullType(TimelineStatusLiteralRt), - timelineType: unionWithNullType(TimelineTypeLiteralRt), -}); - -export type GetTimelinesArgs = runtimeTypes.TypeOf; - -export interface ColumnHeaderResult { - aggregatable?: Maybe; - category?: Maybe; - columnHeaderType?: Maybe; - description?: Maybe; - example?: Maybe; - indexes?: Maybe; - id?: Maybe; - name?: Maybe; - placeholder?: Maybe; - searchable?: Maybe; - type?: Maybe; -} - -export interface DataProviderResult { - id?: Maybe; - name?: Maybe; - enabled?: Maybe; - excluded?: Maybe; - kqlQuery?: Maybe; - queryMatch?: Maybe; - type?: Maybe; - and?: Maybe; -} - -export interface QueryMatchResult { - field?: Maybe; - displayField?: Maybe; - value?: Maybe; - displayValue?: Maybe; - operator?: Maybe; -} - -export interface DateRangePickerResult { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - start?: Maybe; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - end?: Maybe; -} - -export interface EqlOptionsResult { - eventCategoryField?: Maybe; - tiebreakerField?: Maybe; - timestampField?: Maybe; - query?: Maybe; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - size?: Maybe; -} - -export interface FilterTimelineResult { - exists?: Maybe; - meta?: Maybe; - match_all?: Maybe; - missing?: Maybe; - query?: Maybe; - range?: Maybe; - script?: Maybe; -} - -export interface FilterMetaTimelineResult { - alias?: Maybe; - controlledBy?: Maybe; - disabled?: Maybe; - field?: Maybe; - formattedValue?: Maybe; - index?: Maybe; - key?: Maybe; - negate?: Maybe; - params?: Maybe; - type?: Maybe; - value?: Maybe; -} - -export interface SerializedFilterQueryResult { - filterQuery?: Maybe; -} - -export interface KueryFilterQueryResult { - kind?: Maybe; - expression?: Maybe; -} - -export interface SerializedKueryQueryResult { - kuery?: Maybe; - serializedQuery?: Maybe; -} - -export interface TimelineResult { - columns?: Maybe; - created?: Maybe; - createdBy?: Maybe; - dataProviders?: Maybe; - dataViewId?: Maybe; - dateRange?: Maybe; - description?: Maybe; - eqlOptions?: Maybe; - eventIdToNoteIds?: Maybe; - eventType?: Maybe; - excludedRowRendererIds?: Maybe; - favorite?: Maybe; - filters?: Maybe; - kqlMode?: Maybe; - kqlQuery?: Maybe; - indexNames?: Maybe; - notes?: Maybe; - noteIds?: Maybe; - pinnedEventIds?: Maybe; - pinnedEventsSaveObject?: Maybe; - savedQueryId?: Maybe; - savedObjectId: string; - sort?: Maybe; - status?: Maybe; - title?: Maybe; - templateTimelineId?: Maybe; - templateTimelineVersion?: Maybe; - timelineType?: Maybe; - updated?: Maybe; - updatedBy?: Maybe; - version: string; - savedSearchId?: Maybe; -} - -export interface ResponseTimeline { - code?: Maybe; - message?: Maybe; - timeline: TimelineResult; -} - export interface SortTimeline { sortField: SortFieldTimeline; - sortOrder: Direction; -} - -export interface GetAllTimelineVariables { - pageInfo: PageInfoTimeline; - search?: Maybe; - sort?: Maybe; - onlyUserFavorite?: Maybe; - timelineType?: Maybe; - status?: Maybe; + sortOrder: SortDirection; } diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts index 990b19d6f3bab..93d627f53263b 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts @@ -42,24 +42,24 @@ export const TemplateTimelineTypeEnum = TemplateTimelineType.enum; export type ColumnHeaderResult = z.infer; export const ColumnHeaderResult = z.object({ - aggregatable: z.boolean().optional(), - category: z.string().optional(), - columnHeaderType: z.string().optional(), - description: z.string().optional(), - example: z.union([z.string(), z.number()]).optional(), - indexes: z.array(z.string()).optional(), - id: z.string().optional(), - name: z.string().optional(), - placeholder: z.string().optional(), - searchable: z.boolean().optional(), - type: z.string().optional(), + aggregatable: z.boolean().nullable().optional(), + category: z.string().nullable().optional(), + columnHeaderType: z.string().nullable().optional(), + description: z.string().nullable().optional(), + example: z.string().nullable().optional(), + indexes: z.array(z.string()).nullable().optional(), + id: z.string().nullable().optional(), + name: z.string().nullable().optional(), + placeholder: z.string().nullable().optional(), + searchable: z.boolean().nullable().optional(), + type: z.string().nullable().optional(), }); export type QueryMatchResult = z.infer; export const QueryMatchResult = z.object({ field: z.string().nullable().optional(), displayField: z.string().nullable().optional(), - value: z.string().nullable().optional(), + value: z.union([z.string().nullable(), z.array(z.string()).nullable()]).optional(), displayValue: z.string().nullable().optional(), operator: z.string().nullable().optional(), }); @@ -71,7 +71,8 @@ export const DataProviderQueryMatch = z.object({ id: z.string().nullable().optional(), kqlQuery: z.string().nullable().optional(), name: z.string().nullable().optional(), - queryMatch: QueryMatchResult.optional(), + queryMatch: QueryMatchResult.nullable().optional(), + type: DataProviderType.nullable().optional(), }); export type DataProviderResult = z.infer; @@ -119,27 +120,28 @@ export const FavoriteTimelineResult = z.object({ export type FilterTimelineResult = z.infer; export const FilterTimelineResult = z.object({ - exists: z.boolean().optional(), + exists: z.string().nullable().optional(), meta: z .object({ - alias: z.string().optional(), - controlledBy: z.string().optional(), - disabled: z.boolean().optional(), - field: z.string().optional(), - formattedValue: z.string().optional(), - index: z.string().optional(), - key: z.string().optional(), - negate: z.boolean().optional(), - params: z.string().optional(), - type: z.string().optional(), - value: z.string().optional(), + alias: z.string().nullable().optional(), + controlledBy: z.string().nullable().optional(), + disabled: z.boolean().nullable().optional(), + field: z.string().nullable().optional(), + formattedValue: z.string().nullable().optional(), + index: z.string().nullable().optional(), + key: z.string().nullable().optional(), + negate: z.boolean().nullable().optional(), + params: z.string().nullable().optional(), + type: z.string().nullable().optional(), + value: z.string().nullable().optional(), }) + .nullable() .optional(), - match_all: z.string().optional(), - missing: z.string().optional(), - query: z.string().optional(), - range: z.string().optional(), - script: z.string().optional(), + match_all: z.string().nullable().optional(), + missing: z.string().nullable().optional(), + query: z.string().nullable().optional(), + range: z.string().nullable().optional(), + script: z.string().nullable().optional(), }); export type SerializedFilterQueryResult = z.infer; @@ -178,8 +180,8 @@ export const SavedTimeline = z.object({ dataViewId: z.string().nullable().optional(), dateRange: z .object({ - end: z.union([z.string(), z.number()]).optional(), - start: z.union([z.string(), z.number()]).optional(), + end: z.union([z.string().nullable(), z.number().nullable()]).optional(), + start: z.union([z.string().nullable(), z.number().nullable()]).optional(), }) .nullable() .optional(), @@ -213,6 +215,14 @@ export const SavedTimeline = z.object({ updatedBy: z.string().nullable().optional(), }); +export type SavedTimelineWithSavedObjectId = z.infer; +export const SavedTimelineWithSavedObjectId = SavedTimeline.merge( + z.object({ + savedObjectId: z.string(), + version: z.string(), + }) +); + export type BareNote = z.infer; export const BareNote = z.object({ eventId: z.string().nullable().optional(), @@ -251,18 +261,50 @@ export const PinnedEvent = BarePinnedEvent.merge( ); export type TimelineResponse = z.infer; -export const TimelineResponse = SavedTimeline.merge( +export const TimelineResponse = SavedTimeline.merge(SavedTimelineWithSavedObjectId).merge( + z.object({ + eventIdToNoteIds: z.array(Note).nullable().optional(), + notes: z.array(Note).nullable().optional(), + noteIds: z.array(z.string()).nullable().optional(), + pinnedEventIds: z.array(z.string()).nullable().optional(), + pinnedEventsSaveObject: z.array(PinnedEvent).nullable().optional(), + }) +); + +export type TimelineSavedToReturnObject = z.infer; +export const TimelineSavedToReturnObject = SavedTimeline.merge( z.object({ - eventIdToNoteIds: z.array(Note).optional(), - notes: z.array(Note).optional(), - noteIds: z.array(z.string()).optional(), - pinnedEventIds: z.array(z.string()).optional(), - pinnedEventsSaveObject: z.array(PinnedEvent).optional(), savedObjectId: z.string(), version: z.string(), + eventIdToNoteIds: z.array(Note).nullable().optional(), + notes: z.array(Note).nullable().optional(), + noteIds: z.array(z.string()).nullable().optional(), + pinnedEventIds: z.array(z.string()).nullable().optional(), + pinnedEventsSaveObject: z.array(PinnedEvent).nullable().optional(), }) ); +export type SavedObjectResolveOutcome = z.infer; +export const SavedObjectResolveOutcome = z.enum(['exactMatch', 'aliasMatch', 'conflict']); +export type SavedObjectResolveOutcomeEnum = typeof SavedObjectResolveOutcome.enum; +export const SavedObjectResolveOutcomeEnum = SavedObjectResolveOutcome.enum; + +export type SavedObjectResolveAliasPurpose = z.infer; +export const SavedObjectResolveAliasPurpose = z.enum([ + 'savedObjectConversion', + 'savedObjectImport', +]); +export type SavedObjectResolveAliasPurposeEnum = typeof SavedObjectResolveAliasPurpose.enum; +export const SavedObjectResolveAliasPurposeEnum = SavedObjectResolveAliasPurpose.enum; + +export type ResolvedTimeline = z.infer; +export const ResolvedTimeline = z.object({ + timeline: TimelineSavedToReturnObject, + outcome: SavedObjectResolveOutcome, + alias_target_id: z.string().optional(), + alias_purpose: SavedObjectResolveAliasPurpose.optional(), +}); + export type FavoriteTimelineResponse = z.infer; export const FavoriteTimelineResponse = z.object({ savedObjectId: z.string(), @@ -275,6 +317,15 @@ export const FavoriteTimelineResponse = z.object({ favorite: z.array(FavoriteTimelineResult).optional(), }); +export type PersistTimelineResponse = z.infer; +export const PersistTimelineResponse = z.object({ + data: z.object({ + persistTimeline: z.object({ + timeline: TimelineResponse, + }), + }), +}); + export type BareNoteWithoutExternalRefs = z.infer; export const BareNoteWithoutExternalRefs = z.object({ eventId: z.string().nullable().optional(), @@ -306,6 +357,11 @@ export const SortFieldTimeline = z.enum(['title', 'description', 'updated', 'cre export type SortFieldTimelineEnum = typeof SortFieldTimeline.enum; export const SortFieldTimelineEnum = SortFieldTimeline.enum; +export type SortDirection = z.infer; +export const SortDirection = z.enum(['asc', 'desc']); +export type SortDirectionEnum = typeof SortDirection.enum; +export const SortDirectionEnum = SortDirection.enum; + /** * The status of the timeline. Valid values are `active`, `draft`, and `immutable`. */ @@ -317,11 +373,11 @@ export const TimelineStatusEnum = TimelineStatus.enum; export type ImportTimelines = z.infer; export const ImportTimelines = SavedTimeline.merge( z.object({ - savedObjectId: z.string().nullable().optional(), - version: z.string().nullable().optional(), - globalNotes: z.array(BareNote).nullable().optional(), - eventNotes: z.array(BareNote).nullable().optional(), - pinnedEventIds: z.array(z.string()).nullable().optional(), + savedObjectId: z.string().nullable(), + version: z.string().nullable(), + pinnedEventIds: z.array(z.string()).nullable(), + eventNotes: z.array(BareNote).nullable(), + globalNotes: z.array(BareNote).nullable(), }) ); @@ -346,24 +402,14 @@ export const ImportTimelineResult = z.object({ .optional(), }); -export type ExportedTimelines = z.infer; -export const ExportedTimelines = SavedTimeline.merge( +export type TimelineErrorResponse = z.infer; +export const TimelineErrorResponse = z.union([ z.object({ - globalNotes: z.array(Note).optional(), - eventNotes: z.array(Note).optional(), - pinnedEventIds: z.array(z.string()).optional(), - }) -); - -export type Readable = z.infer; -export const Readable = z.object({ - _maxListeners: z.object({}).catchall(z.unknown()).optional(), - _readableState: z.object({}).catchall(z.unknown()).optional(), - _read: z.object({}).catchall(z.unknown()).optional(), - readable: z.boolean().optional(), - _events: z.object({}).catchall(z.unknown()).optional(), - _eventsCount: z.number().optional(), - _data: z.object({}).catchall(z.unknown()).optional(), - _position: z.number().optional(), - _encoding: z.string().optional(), -}); + message: z.string(), + status_code: z.number(), + }), + z.object({ + message: z.string(), + statusCode: z.number(), + }), +]); diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml index c8ba2e6019f16..568eec1975769 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml @@ -56,11 +56,15 @@ components: end: oneOf: - type: string + nullable: true - type: number + nullable: true start: oneOf: - type: string + nullable: true - type: number + nullable: true description: type: string nullable: true @@ -149,43 +153,73 @@ components: updatedBy: type: string nullable: true + SavedTimelineWithSavedObjectId: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + required: [savedObjectId, version] + properties: + savedObjectId: + type: string + version: + type: string TimelineResponse: allOf: - $ref: '#/components/schemas/SavedTimeline' + - $ref: '#/components/schemas/SavedTimelineWithSavedObjectId' - type: object - required: - - savedObjectId - - version properties: eventIdToNoteIds: type: array + nullable: true items: $ref: '#/components/schemas/Note' notes: type: array + nullable: true items: $ref: '#/components/schemas/Note' noteIds: type: array + nullable: true items: type: string pinnedEventIds: type: array + nullable: true items: type: string pinnedEventsSaveObject: type: array + nullable: true items: $ref: '#/components/schemas/PinnedEvent' - savedObjectId: - type: string - version: - type: string + ResolvedTimeline: + type: object + required: [timeline, outcome] + properties: + timeline: + $ref: '#/components/schemas/TimelineSavedToReturnObject' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + alias_target_id: + type: string + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + SavedObjectResolveOutcome: + type: string + enum: + - exactMatch + - aliasMatch + - conflict + SavedObjectResolveAliasPurpose: + type: string + enum: + - savedObjectConversion + - savedObjectImport FavoriteTimelineResponse: type: object - required: - - savedObjectId - - version + required: [savedObjectId, version] properties: savedObjectId: type: string @@ -209,35 +243,58 @@ components: type: array items: $ref: '#/components/schemas/FavoriteTimelineResult' + PersistTimelineResponse: + type: object + required: [data] + properties: + data: + type: object + required: [persistTimeline] + properties: + persistTimeline: + type: object + required: [timeline] + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' ColumnHeaderResult: type: object properties: aggregatable: type: boolean + nullable: true category: type: string + nullable: true columnHeaderType: type: string + nullable: true description: type: string + nullable: true example: - oneOf: - - type: string - - type: number + type: string + nullable: true indexes: type: array + nullable: true items: type: string id: type: string + nullable: true name: type: string + nullable: true placeholder: type: string + nullable: true searchable: type: boolean + nullable: true type: type: string + nullable: true QueryMatchResult: type: object properties: @@ -248,8 +305,13 @@ components: type: string nullable: true value: - type: string - nullable: true + oneOf: + - type: string + nullable: true + - type: array + nullable: true + items: + type: string displayValue: type: string nullable: true @@ -305,6 +367,10 @@ components: nullable: true queryMatch: $ref: '#/components/schemas/QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/DataProviderType' + nullable: true BareNoteWithoutExternalRefs: type: object properties: @@ -419,42 +485,60 @@ components: type: object properties: exists: - type: boolean + type: string + nullable: true meta: type: object + nullable: true properties: alias: type: string + nullable: true controlledBy: type: string + nullable: true disabled: type: boolean + nullable: true field: type: string + nullable: true formattedValue: type: string + nullable: true index: type: string + nullable: true key: type: string + nullable: true negate: type: boolean + nullable: true params: type: string + nullable: true type: type: string + nullable: true value: type: string + nullable: true match_all: type: string + nullable: true missing: type: string + nullable: true query: type: string + nullable: true range: type: string + nullable: true script: type: string + nullable: true SerializedFilterQueryResult: type: object properties: @@ -531,6 +615,11 @@ components: - description - updated - created + SortDirection: + type: string + enum: + - asc + - desc TimelineStatus: type: string enum: @@ -544,6 +633,7 @@ components: allOf: - $ref: '#/components/schemas/SavedTimeline' - type: object + required: [savedObjectId, version, pinnedEventIds, eventNotes, globalNotes] properties: savedObjectId: type: string @@ -551,21 +641,56 @@ components: version: type: string nullable: true - globalNotes: - nullable: true + pinnedEventIds: type: array + nullable: true items: - $ref: '#/components/schemas/BareNote' + type: string eventNotes: + type: array nullable: true + items: + $ref: '#/components/schemas/BareNote' + globalNotes: type: array + nullable: true items: $ref: '#/components/schemas/BareNote' - pinnedEventIds: + TimelineSavedToReturnObject: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + required: [savedObjectId, version] + properties: + savedObjectId: + type: string + version: + type: string + eventIdToNoteIds: + type: array nullable: true + items: + $ref: '#/components/schemas/Note' + notes: + type: array + nullable: true + items: + $ref: '#/components/schemas/Note' + noteIds: + type: array + nullable: true + items: + type: string + pinnedEventIds: type: array + nullable: true items: type: string + pinnedEventsSaveObject: + type: array + nullable: true + items: + $ref: '#/components/schemas/PinnedEvent' ImportTimelineResult: type: object properties: @@ -591,46 +716,19 @@ components: type: string status_code: type: number - ExportedTimelines: - allOf: - - $ref: '#/components/schemas/SavedTimeline' + TimelineErrorResponse: + oneOf: - type: object + required: [message, status_code] properties: - globalNotes: - type: array - items: - $ref: '#/components/schemas/Note' - eventNotes: - type: array - items: - $ref: '#/components/schemas/Note' - pinnedEventIds: - type: array - items: - type: string - Readable: - type: object - properties: - _maxListeners: - type: object - additionalProperties: true - _readableState: - type: object - additionalProperties: true - _read: - type: object - additionalProperties: true - readable: - type: boolean - _events: - type: object - additionalProperties: true - _eventsCount: - type: number - _data: - type: object - additionalProperties: true - _position: - type: number - _encoding: - type: string + message: + type: string + status_code: + type: number + - type: object + required: [message, statusCode] + properties: + message: + type: string + statusCode: + type: number diff --git a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.gen.ts index d0f2befe6b338..622c56312e21a 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.gen.ts @@ -16,7 +16,7 @@ import { z } from '@kbn/zod'; -import { SavedTimeline, TimelineResponse } from '../model/components.gen'; +import { SavedTimeline, PersistTimelineResponse } from '../model/components.gen'; export type PatchTimelineRequestBody = z.infer; export const PatchTimelineRequestBody = z.object({ @@ -27,10 +27,4 @@ export const PatchTimelineRequestBody = z.object({ export type PatchTimelineRequestBodyInput = z.input; export type PatchTimelineResponse = z.infer; -export const PatchTimelineResponse = z.object({ - data: z.object({ - persistTimeline: z.object({ - timeline: TimelineResponse, - }), - }), -}); +export const PatchTimelineResponse = PersistTimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.schema.yaml index 04d6de3cc3f87..3ac7e9b748ed7 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Patch Timeline API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline: patch: @@ -42,19 +35,7 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [persistTimeline] - properties: - persistTimeline: - type: object - required: [timeline] - properties: - timeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + $ref: '../model/components.schema.yaml#/components/schemas/PersistTimelineResponse' '405': description: Indicates that the user does not have the required access to create a draft timeline. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timelines_schema.ts b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timelines_schema.ts deleted file mode 100644 index 149dc24480d2d..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timelines_schema.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 * as rt from 'io-ts'; - -import type { ResponseTimeline } from '../model/api'; -import { SavedTimelineRuntimeType } from '../model/api'; -import { unionWithNullType } from '../../../utility_types'; - -export const patchTimelineSchema = rt.type({ - timeline: SavedTimelineRuntimeType, - timelineId: unionWithNullType(rt.string), - version: unionWithNullType(rt.string), -}); - -export interface PatchTimelinesResponse { - data: { - persistTimeline: ResponseTimeline; - }; -} diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml index 87a9e4d21ac68..c041280fbdc15 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Favorite API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/_favorite: patch: 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 a74b50f4156e2..d0129fe39fdab 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 @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/timeline-api-update.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/note: patch: 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 fc54c7aaeb1b2..486b8b5bae390 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 @@ -5,13 +5,6 @@ info: externalDocs: url: https://www.elastic.co/guide/en/security/current/_pin_an_event_to_an_existing_timeline.html description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/pinned_event: patch: 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 deleted file mode 100644 index 31c3233e9b8ca..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 * as runtimeTypes from 'io-ts'; -import { unionWithNullType } from '../../../utility_types'; - -/* - * Pinned Event Types - * TODO: remove these when the timeline types are moved to zod - */ -const BarePinnedEventType = runtimeTypes.intersection([ - runtimeTypes.type({ - timelineId: runtimeTypes.string, - eventId: runtimeTypes.string, - }), - runtimeTypes.partial({ - created: unionWithNullType(runtimeTypes.number), - createdBy: unionWithNullType(runtimeTypes.string), - updated: unionWithNullType(runtimeTypes.number), - updatedBy: unionWithNullType(runtimeTypes.string), - }), -]); - -export const PinnedEventRuntimeType = runtimeTypes.intersection([ - runtimeTypes.type({ - pinnedEventId: runtimeTypes.string, - version: runtimeTypes.string, - }), - BarePinnedEventType, -]); diff --git a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts index 2c3b966bfb1b4..d4c79eec50b26 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts @@ -16,7 +16,7 @@ import { z } from '@kbn/zod'; -import { TimelineResponse } from '../model/components.gen'; +import { ResolvedTimeline } from '../model/components.gen'; export type ResolveTimelineRequestQuery = z.infer; export const ResolveTimelineRequestQuery = z.object({ @@ -32,8 +32,9 @@ export const ResolveTimelineRequestQuery = z.object({ export type ResolveTimelineRequestQueryInput = z.input; export type ResolveTimelineResponse = z.infer; -export const ResolveTimelineResponse = z.object({ - data: z.object({ - getOneTimeline: TimelineResponse.nullable(), +export const ResolveTimelineResponse = z.union([ + z.object({ + data: ResolvedTimeline, }), -}); + z.object({}).strict(), +]); diff --git a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml index 6c2b0d309f01f..8c148be90e062 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml @@ -2,13 +2,6 @@ openapi: 3.0.0 info: title: Elastic Security - Timeline - Resolve Timeline API version: '2023-10-31' -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' paths: /api/timeline/resolve: get: @@ -35,16 +28,15 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [getOneTimeline] + oneOf: + - type: object + required: [data] properties: - getOneTimeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' - nullable: true + data: + $ref: '../model/components.schema.yaml#/components/schemas/ResolvedTimeline' + - type: object + additionalProperties: false + '400': description: The request is missing parameters '404': diff --git a/x-pack/plugins/security_solution/common/api/timeline/routes.ts b/x-pack/plugins/security_solution/common/api/timeline/routes.ts index 9d3aec839a5c1..70b339c92f197 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/routes.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/routes.ts @@ -17,7 +17,10 @@ export { } from './persist_note/persist_note_route.gen'; export { DeleteNoteRequestBody, DeleteNoteResponse } from './delete_note/delete_note_route.gen'; -export { CleanDraftTimelinesRequestBody } from './clean_draft_timelines/clean_draft_timelines_route.gen'; +export { + CleanDraftTimelinesResponse, + CleanDraftTimelinesRequestBody, +} from './clean_draft_timelines/clean_draft_timelines_route.gen'; export { ExportTimelinesRequestQuery, @@ -40,3 +43,48 @@ export { GetNotesResponse, GetNotesResult, } from './get_notes/get_notes_route.gen'; + +export { + CopyTimelineRequestBody, + CopyTimelineResponse, +} from './copy_timeline/copy_timeline_route.gen'; + +export { + CreateTimelinesRequestBody, + CreateTimelinesResponse, +} from './create_timelines/create_timelines_route.gen'; + +export { + PatchTimelineRequestBody, + PatchTimelineResponse, +} from './patch_timelines/patch_timeline_route.gen'; + +export { + ImportTimelinesRequestBody, + ImportTimelinesResponse, +} from './import_timelines/import_timelines_route.gen'; + +export { + InstallPrepackedTimelinesRequestBody, + InstallPrepackedTimelinesResponse, +} from './install_prepackaged_timelines/install_prepackaged_timelines_route.gen'; + +export { + GetDraftTimelinesRequestQuery, + GetDraftTimelinesResponse, +} from './get_draft_timelines/get_draft_timelines_route.gen'; + +export { + ResolveTimelineRequestQuery, + ResolveTimelineResponse, +} from './resolve_timeline/resolve_timeline_route.gen'; + +export { + GetTimelineRequestQuery, + GetTimelineResponse, +} from './get_timeline/get_timeline_route.gen'; + +export { + GetTimelinesRequestQuery, + GetTimelinesResponse, +} from './get_timelines/get_timelines_route.gen'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts index ff9613503e8e3..7fe70febe4e2c 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts @@ -5,13 +5,7 @@ * 2.0. */ -import type { SortField, Maybe } from '../common'; -import type { - DataProviderType, - TimelineType, - TimelineStatus, - RowRendererId, -} from '../../api/timeline'; +import type { SortField } from '../common'; export * from './events'; @@ -20,122 +14,6 @@ export interface TimelineRequestSortField extends SortField; - category?: Maybe; - columnHeaderType?: Maybe; - description?: Maybe; - example?: Maybe; - indexes?: Maybe; - id?: Maybe; - name?: Maybe; - placeholder?: Maybe; - searchable?: Maybe; - type?: Maybe; -} - -export interface QueryMatchInput { - field?: Maybe; - - displayField?: Maybe; - - value?: Maybe; - - displayValue?: Maybe; - - operator?: Maybe; -} - -export interface DataProviderInput { - id?: Maybe; - name?: Maybe; - enabled?: Maybe; - excluded?: Maybe; - kqlQuery?: Maybe; - queryMatch?: Maybe; - and?: Maybe; - type?: Maybe; -} - -export interface EqlOptionsInput { - eventCategoryField?: Maybe; - tiebreakerField?: Maybe; - timestampField?: Maybe; - query?: Maybe; - size?: Maybe; -} - -export interface FilterMetaTimelineInput { - alias?: Maybe; - controlledBy?: Maybe; - disabled?: Maybe; - field?: Maybe; - formattedValue?: Maybe; - index?: Maybe; - key?: Maybe; - negate?: Maybe; - params?: Maybe; - type?: Maybe; - value?: Maybe; -} - -export interface FilterTimelineInput { - exists?: Maybe; - meta?: Maybe; - match_all?: Maybe; - missing?: Maybe; - query?: Maybe; - range?: Maybe; - script?: Maybe; -} - -export interface SerializedFilterQueryInput { - filterQuery?: Maybe; -} - -export interface SerializedKueryQueryInput { - kuery?: Maybe; - serializedQuery?: Maybe; -} - -export interface KueryFilterQueryInput { - kind?: Maybe; - expression?: Maybe; -} - -export interface DateRangePickerInput { - start?: Maybe; - end?: Maybe; -} - -export interface SortTimelineInput { - columnId?: Maybe; - sortDirection?: Maybe; -} - -export interface TimelineInput { - columns?: Maybe; - dataProviders?: Maybe; - dataViewId?: Maybe; - description?: Maybe; - eqlOptions?: Maybe; - eventType?: Maybe; - excludedRowRendererIds?: Maybe; - filters?: Maybe; - kqlMode?: Maybe; - kqlQuery?: Maybe; - indexNames?: Maybe; - title?: Maybe; - templateTimelineId?: Maybe; - templateTimelineVersion?: Maybe; - timelineType?: Maybe; - dateRange?: Maybe; - savedQueryId?: Maybe; - sort?: Maybe; - status?: Maybe; - savedSearchId: Maybe; -} - export enum FlowDirection { uniDirectional = 'uniDirectional', biDirectional = 'biDirectional', diff --git a/x-pack/plugins/security_solution/common/timelines/zod_errors.ts b/x-pack/plugins/security_solution/common/timelines/zod_errors.ts new file mode 100644 index 0000000000000..7ef5606a4dcfb --- /dev/null +++ b/x-pack/plugins/security_solution/common/timelines/zod_errors.ts @@ -0,0 +1,29 @@ +/* + * 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 type { ZodError, ZodType } from '@kbn/zod'; +import { stringifyZodError } from '@kbn/zod-helpers'; +import { type Either, fold, left, right } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; + +type ErrorFactory = (message: string) => Error; + +const throwErrors = (createError: ErrorFactory) => (errors: ZodError) => { + throw createError(stringifyZodError(errors)); +}; + +const parseRuntimeType = + (zodType: ZodType) => + (v: unknown): Either, T> => { + const result = zodType.safeParse(v); + return result.success ? right(result.data) : left(result.error); + }; + +export const parseOrThrowErrorFactory = + (createError: ErrorFactory) => (runtimeType: ZodType) => (inputValue: unknown) => + pipe(parseRuntimeType(runtimeType)(inputValue), fold(throwErrors(createError), identity)); diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml index 1c8cbebd9f52c..52f4f7daffeb7 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml @@ -263,18 +263,20 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: '#/components/schemas/TimelineResponse' - nullable: true + data: + type: object + properties: + getOneTimeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - getOneTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: Indicates that the (template) timeline was found and returned. summary: >- Get an existing saved timeline or timeline template. This API is used to @@ -313,22 +315,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: >- Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft @@ -389,20 +376,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: Indicates the timeline was successfully created. '405': content: @@ -419,6 +393,36 @@ paths: tags: - Security Timeline API - 'access:securitySolution' + /api/timeline/_copy: + get: + description: | + Copies and returns a timeline or timeline template. + operationId: CopyTimeline + requestBody: + content: + application/json: + schema: + type: object + properties: + timeline: + $ref: '#/components/schemas/SavedTimeline' + timelineIdToCopy: + type: string + required: + - timeline + - timelineIdToCopy + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PersistTimelineResponse' + description: Indicates that the timeline has been successfully copied. + summary: Copies timeline or timeline template + tags: + - Security Timeline API + - 'access:securitySolution' /api/timeline/_draft: get: operationId: GetDraftTimelines @@ -433,22 +437,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: Indicates that the draft timeline was successfully retrieved. '403': content: @@ -508,22 +497,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: >- Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft @@ -675,28 +649,14 @@ paths: schema: type: object properties: - file: - allOf: - - $ref: '#/components/schemas/Readable' - - type: object - properties: - hapi: - type: object - properties: - filename: - type: string - headers: - type: object - isImmutable: - enum: - - 'true' - - 'false' - type: string - required: - - filename - - headers - required: - - hapi + file: {} + isImmutable: + enum: + - 'true' + - 'false' + type: string + required: + - file description: The timelines to import as a readable stream. required: true responses: @@ -704,12 +664,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - $ref: '#/components/schemas/ImportTimelineResult' - required: - - data + $ref: '#/components/schemas/ImportTimelineResult' description: Indicates the import of timelines was successful. '400': content: @@ -767,7 +722,8 @@ paths: properties: prepackagedTimelines: items: - $ref: '#/components/schemas/SavedTimeline' + $ref: '#/components/schemas/TimelineSavedToReturnObject' + nullable: true type: array timelinesToInstall: items: @@ -790,12 +746,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - $ref: '#/components/schemas/ImportTimelineResult' - required: - - data + $ref: '#/components/schemas/ImportTimelineResult' description: Indicates the installation of prepackaged timelines was successful. '500': content: @@ -833,18 +784,15 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: '#/components/schemas/TimelineResponse' - nullable: true + data: + $ref: '#/components/schemas/ResolvedTimeline' required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: The (template) timeline has been found '400': description: The request is missing parameters @@ -912,35 +860,25 @@ paths: schema: type: object properties: - data: - type: object - properties: - customTemplateTimelineCount: - type: number - defaultTimelineCount: - type: number - elasticTemplateTimelineCount: - type: number - favoriteCount: - type: number - templateTimelineCount: - type: number - timelines: - items: - $ref: '#/components/schemas/TimelineResponse' - type: array - totalCount: - type: number - required: - - timelines - - totalCount - - defaultTimelineCount - - templateTimelineCount - - favoriteCount - - elasticTemplateTimelineCount - - customTemplateTimelineCount + customTemplateTimelineCount: + type: number + defaultTimelineCount: + type: number + elasticTemplateTimelineCount: + type: number + favoriteCount: + type: number + templateTimelineCount: + type: number + timeline: + items: + $ref: '#/components/schemas/TimelineResponse' + type: array + totalCount: + type: number required: - - data + - timeline + - totalCount description: Indicates that the (template) timelines were found and returned. '400': content: @@ -1012,30 +950,39 @@ components: type: object properties: aggregatable: + nullable: true type: boolean category: + nullable: true type: string columnHeaderType: + nullable: true type: string description: + nullable: true type: string example: - oneOf: - - type: string - - type: number + nullable: true + type: string id: + nullable: true type: string indexes: items: type: string + nullable: true type: array name: + nullable: true type: string placeholder: + nullable: true type: string searchable: + nullable: true type: boolean type: + nullable: true type: string DataProviderQueryMatch: type: object @@ -1057,6 +1004,10 @@ components: type: string queryMatch: $ref: '#/components/schemas/QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/DataProviderType' + nullable: true DataProviderResult: type: object properties: @@ -1144,41 +1095,59 @@ components: type: object properties: exists: - type: boolean + nullable: true + type: string match_all: + nullable: true type: string meta: + nullable: true type: object properties: alias: + nullable: true type: string controlledBy: + nullable: true type: string disabled: + nullable: true type: boolean field: + nullable: true type: string formattedValue: + nullable: true type: string index: + nullable: true type: string key: + nullable: true type: string negate: + nullable: true type: boolean params: + nullable: true type: string type: + nullable: true type: string value: + nullable: true type: string missing: + nullable: true type: string query: + nullable: true type: string range: + nullable: true type: string script: + nullable: true type: string GetNotesResult: type: object @@ -1243,6 +1212,12 @@ components: version: nullable: true type: string + required: + - savedObjectId + - version + - pinnedEventIds + - eventNotes + - globalNotes Note: allOf: - $ref: '#/components/schemas/BareNote' @@ -1262,6 +1237,23 @@ components: - $ref: '#/components/schemas/PinnedEventBaseResponseBody' - nullable: true type: object + PersistTimelineResponse: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data PinnedEvent: allOf: - $ref: '#/components/schemas/BarePinnedEvent' @@ -1299,34 +1291,27 @@ components: nullable: true type: string value: - nullable: true - type: string - Readable: + oneOf: + - nullable: true + type: string + - items: + type: string + nullable: true + type: array + ResolvedTimeline: type: object properties: - _data: - additionalProperties: true - type: object - _encoding: + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: type: string - _events: - additionalProperties: true - type: object - _eventsCount: - type: number - _maxListeners: - additionalProperties: true - type: object - _position: - type: number - _read: - additionalProperties: true - type: object - _readableState: - additionalProperties: true - type: object - readable: - type: boolean + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + timeline: + $ref: '#/components/schemas/TimelineSavedToReturnObject' + required: + - timeline + - outcome ResponseNote: type: object properties: @@ -1361,6 +1346,17 @@ components: - threat_match - zeek type: string + SavedObjectResolveAliasPurpose: + enum: + - savedObjectConversion + - savedObjectImport + type: string + SavedObjectResolveOutcome: + enum: + - exactMatch + - aliasMatch + - conflict + type: string SavedTimeline: type: object properties: @@ -1389,12 +1385,16 @@ components: properties: end: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number start: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number description: nullable: true type: string @@ -1483,6 +1483,18 @@ components: updatedBy: nullable: true type: string + SavedTimelineWithSavedObjectId: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + savedObjectId: + type: string + version: + type: string + required: + - savedObjectId + - version SerializedFilterQueryResult: type: object properties: @@ -1532,27 +1544,63 @@ components: TimelineResponse: allOf: - $ref: '#/components/schemas/SavedTimeline' + - $ref: '#/components/schemas/SavedTimelineWithSavedObjectId' - type: object properties: eventIdToNoteIds: items: $ref: '#/components/schemas/Note' + nullable: true type: array noteIds: items: type: string + nullable: true type: array notes: items: $ref: '#/components/schemas/Note' + nullable: true type: array pinnedEventIds: items: type: string + nullable: true type: array pinnedEventsSaveObject: items: $ref: '#/components/schemas/PinnedEvent' + nullable: true + type: array + TimelineSavedToReturnObject: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + eventIdToNoteIds: + items: + $ref: '#/components/schemas/Note' + nullable: true + type: array + noteIds: + items: + type: string + nullable: true + type: array + notes: + items: + $ref: '#/components/schemas/Note' + nullable: true + type: array + pinnedEventIds: + items: + type: string + nullable: true + type: array + pinnedEventsSaveObject: + items: + $ref: '#/components/schemas/PinnedEvent' + nullable: true type: array savedObjectId: type: string diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml index 1148926af4689..442d00dc3c1bb 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml @@ -263,18 +263,20 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: '#/components/schemas/TimelineResponse' - nullable: true + data: + type: object + properties: + getOneTimeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - getOneTimeline required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: Indicates that the (template) timeline was found and returned. summary: >- Get an existing saved timeline or timeline template. This API is used to @@ -313,22 +315,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: >- Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft @@ -389,20 +376,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: Indicates the timeline was successfully created. '405': content: @@ -419,6 +393,36 @@ paths: tags: - Security Timeline API - 'access:securitySolution' + /api/timeline/_copy: + get: + description: | + Copies and returns a timeline or timeline template. + operationId: CopyTimeline + requestBody: + content: + application/json: + schema: + type: object + properties: + timeline: + $ref: '#/components/schemas/SavedTimeline' + timelineIdToCopy: + type: string + required: + - timeline + - timelineIdToCopy + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PersistTimelineResponse' + description: Indicates that the timeline has been successfully copied. + summary: Copies timeline or timeline template + tags: + - Security Timeline API + - 'access:securitySolution' /api/timeline/_draft: get: operationId: GetDraftTimelines @@ -433,22 +437,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: Indicates that the draft timeline was successfully retrieved. '403': content: @@ -508,22 +497,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline - required: - - persistTimeline - required: - - data + $ref: '#/components/schemas/PersistTimelineResponse' description: >- Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft @@ -675,28 +649,14 @@ paths: schema: type: object properties: - file: - allOf: - - $ref: '#/components/schemas/Readable' - - type: object - properties: - hapi: - type: object - properties: - filename: - type: string - headers: - type: object - isImmutable: - enum: - - 'true' - - 'false' - type: string - required: - - filename - - headers - required: - - hapi + file: {} + isImmutable: + enum: + - 'true' + - 'false' + type: string + required: + - file description: The timelines to import as a readable stream. required: true responses: @@ -704,12 +664,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - $ref: '#/components/schemas/ImportTimelineResult' - required: - - data + $ref: '#/components/schemas/ImportTimelineResult' description: Indicates the import of timelines was successful. '400': content: @@ -767,7 +722,8 @@ paths: properties: prepackagedTimelines: items: - $ref: '#/components/schemas/SavedTimeline' + $ref: '#/components/schemas/TimelineSavedToReturnObject' + nullable: true type: array timelinesToInstall: items: @@ -790,12 +746,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - $ref: '#/components/schemas/ImportTimelineResult' - required: - - data + $ref: '#/components/schemas/ImportTimelineResult' description: Indicates the installation of prepackaged timelines was successful. '500': content: @@ -833,18 +784,15 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object + oneOf: + - type: object properties: - getOneTimeline: - $ref: '#/components/schemas/TimelineResponse' - nullable: true + data: + $ref: '#/components/schemas/ResolvedTimeline' required: - - getOneTimeline - required: - - data + - data + - additionalProperties: false + type: object description: The (template) timeline has been found '400': description: The request is missing parameters @@ -912,35 +860,25 @@ paths: schema: type: object properties: - data: - type: object - properties: - customTemplateTimelineCount: - type: number - defaultTimelineCount: - type: number - elasticTemplateTimelineCount: - type: number - favoriteCount: - type: number - templateTimelineCount: - type: number - timelines: - items: - $ref: '#/components/schemas/TimelineResponse' - type: array - totalCount: - type: number - required: - - timelines - - totalCount - - defaultTimelineCount - - templateTimelineCount - - favoriteCount - - elasticTemplateTimelineCount - - customTemplateTimelineCount + customTemplateTimelineCount: + type: number + defaultTimelineCount: + type: number + elasticTemplateTimelineCount: + type: number + favoriteCount: + type: number + templateTimelineCount: + type: number + timeline: + items: + $ref: '#/components/schemas/TimelineResponse' + type: array + totalCount: + type: number required: - - data + - timeline + - totalCount description: Indicates that the (template) timelines were found and returned. '400': content: @@ -1012,30 +950,39 @@ components: type: object properties: aggregatable: + nullable: true type: boolean category: + nullable: true type: string columnHeaderType: + nullable: true type: string description: + nullable: true type: string example: - oneOf: - - type: string - - type: number + nullable: true + type: string id: + nullable: true type: string indexes: items: type: string + nullable: true type: array name: + nullable: true type: string placeholder: + nullable: true type: string searchable: + nullable: true type: boolean type: + nullable: true type: string DataProviderQueryMatch: type: object @@ -1057,6 +1004,10 @@ components: type: string queryMatch: $ref: '#/components/schemas/QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/DataProviderType' + nullable: true DataProviderResult: type: object properties: @@ -1144,41 +1095,59 @@ components: type: object properties: exists: - type: boolean + nullable: true + type: string match_all: + nullable: true type: string meta: + nullable: true type: object properties: alias: + nullable: true type: string controlledBy: + nullable: true type: string disabled: + nullable: true type: boolean field: + nullable: true type: string formattedValue: + nullable: true type: string index: + nullable: true type: string key: + nullable: true type: string negate: + nullable: true type: boolean params: + nullable: true type: string type: + nullable: true type: string value: + nullable: true type: string missing: + nullable: true type: string query: + nullable: true type: string range: + nullable: true type: string script: + nullable: true type: string GetNotesResult: type: object @@ -1243,6 +1212,12 @@ components: version: nullable: true type: string + required: + - savedObjectId + - version + - pinnedEventIds + - eventNotes + - globalNotes Note: allOf: - $ref: '#/components/schemas/BareNote' @@ -1262,6 +1237,23 @@ components: - $ref: '#/components/schemas/PinnedEventBaseResponseBody' - nullable: true type: object + PersistTimelineResponse: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data PinnedEvent: allOf: - $ref: '#/components/schemas/BarePinnedEvent' @@ -1299,34 +1291,27 @@ components: nullable: true type: string value: - nullable: true - type: string - Readable: + oneOf: + - nullable: true + type: string + - items: + type: string + nullable: true + type: array + ResolvedTimeline: type: object properties: - _data: - additionalProperties: true - type: object - _encoding: + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: type: string - _events: - additionalProperties: true - type: object - _eventsCount: - type: number - _maxListeners: - additionalProperties: true - type: object - _position: - type: number - _read: - additionalProperties: true - type: object - _readableState: - additionalProperties: true - type: object - readable: - type: boolean + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + timeline: + $ref: '#/components/schemas/TimelineSavedToReturnObject' + required: + - timeline + - outcome ResponseNote: type: object properties: @@ -1361,6 +1346,17 @@ components: - threat_match - zeek type: string + SavedObjectResolveAliasPurpose: + enum: + - savedObjectConversion + - savedObjectImport + type: string + SavedObjectResolveOutcome: + enum: + - exactMatch + - aliasMatch + - conflict + type: string SavedTimeline: type: object properties: @@ -1389,12 +1385,16 @@ components: properties: end: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number start: oneOf: - - type: string - - type: number + - nullable: true + type: string + - nullable: true + type: number description: nullable: true type: string @@ -1483,6 +1483,18 @@ components: updatedBy: nullable: true type: string + SavedTimelineWithSavedObjectId: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + savedObjectId: + type: string + version: + type: string + required: + - savedObjectId + - version SerializedFilterQueryResult: type: object properties: @@ -1532,27 +1544,63 @@ components: TimelineResponse: allOf: - $ref: '#/components/schemas/SavedTimeline' + - $ref: '#/components/schemas/SavedTimelineWithSavedObjectId' - type: object properties: eventIdToNoteIds: items: $ref: '#/components/schemas/Note' + nullable: true type: array noteIds: items: type: string + nullable: true type: array notes: items: $ref: '#/components/schemas/Note' + nullable: true type: array pinnedEventIds: items: type: string + nullable: true type: array pinnedEventsSaveObject: items: $ref: '#/components/schemas/PinnedEvent' + nullable: true + type: array + TimelineSavedToReturnObject: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + eventIdToNoteIds: + items: + $ref: '#/components/schemas/Note' + nullable: true + type: array + noteIds: + items: + type: string + nullable: true + type: array + notes: + items: + $ref: '#/components/schemas/Note' + nullable: true + type: array + pinnedEventIds: + items: + type: string + nullable: true + type: array + pinnedEventsSaveObject: + items: + $ref: '#/components/schemas/PinnedEvent' + nullable: true type: array savedObjectId: type: 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 427285c5f9233..d32891333c7f0 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 @@ -9,9 +9,10 @@ import { FilterStateStore } from '@kbn/es-query'; import type { DataTableModel } from '@kbn/securitysolution-data-table'; import { VIEW_SELECTION } from '../../../common/constants'; -import type { TimelineResult } from '../../../common/api/timeline'; import { TimelineId, TimelineTabs } from '../../../common/types/timeline'; +import type { TimelineResponse } from '../../../common/api/timeline'; import { + type ColumnHeaderResult, RowRendererIdEnum, TimelineTypeEnum, TimelineStatusEnum, @@ -1986,9 +1987,11 @@ export const mockDataTableModel: DataTableModel = { }, }; -export const mockGetOneTimelineResult: TimelineResult = { +export const mockGetOneTimelineResult: TimelineResponse = { savedObjectId: 'ef579e40-jibber-jabber', - columns: timelineDefaults.columns.filter((column) => column.id !== 'event.action'), + columns: timelineDefaults.columns.filter( + (column) => column.id !== 'event.action' + ) as ColumnHeaderResult[], dateRange: { start: '2020-03-18T13:46:38.929Z', end: '2020-03-18T13:52:38.929Z' }, description: 'This is a sample rule description', eventType: 'all', diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index c83fa2bf22211..a2dfef2c43e9f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -50,8 +50,8 @@ import { isNewTermsRule, isThresholdRule, } from '../../../../common/detection_engine/utils'; -import type { TimelineResult } from '../../../../common/api/timeline'; import { TimelineId } from '../../../../common/types/timeline'; +import type { TimelineResponse } from '../../../../common/api/timeline'; import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import type { SendAlertToTimelineActionProps, @@ -69,7 +69,7 @@ import { TimelineEventsQueries } from '../../../../common/search_strategy/timeli import { timelineDefaults } from '../../../timelines/store/defaults'; import { omitTypenameInTimeline, - formatTimelineResultToModel, + formatTimelineResponseToModel, } from '../../../timelines/components/open_timeline/helpers'; import { convertKueryToElasticSearchQuery } from '../../../common/lib/kuery'; import { getField, getFieldKey } from '../../../helpers'; @@ -983,11 +983,15 @@ export const sendAlertToTimelineAction = async ({ ), ]); - const resultingTimeline: TimelineResult = getOr({}, 'data.getOneTimeline', responseTimeline); + const resultingTimeline: TimelineResponse = getOr( + {}, + 'data.getOneTimeline', + responseTimeline + ); const eventData: TimelineEventsDetailsItem[] = eventDataResp.data ?? []; if (!isEmpty(resultingTimeline)) { - const timelineTemplate: TimelineResult = omitTypenameInTimeline(resultingTimeline); - const { timeline, notes } = formatTimelineResultToModel( + const timelineTemplate = omitTypenameInTimeline(resultingTimeline); + const { timeline, notes } = formatTimelineResponseToModel( timelineTemplate, true, timelineTemplate.timelineType ?? TimelineTypeEnum.default diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index 397e632a5c04c..a3428ae6f2e1d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -20,12 +20,16 @@ import { isUntitled, omitTypenameInTimeline, useQueryTimelineById, - formatTimelineResultToModel, + formatTimelineResponseToModel, } from './helpers'; import type { OpenTimelineResult } from './types'; import { TimelineId } from '../../../../common/types/timeline'; -import type { RowRendererId } from '../../../../common/api/timeline'; -import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline'; +import { + TimelineTypeEnum, + TimelineStatusEnum, + type ColumnHeaderResult, + type RowRendererId, +} from '../../../../common/api/timeline'; import { mockTimeline as mockSelectedTimeline, mockTemplate as mockSelectedTemplate, @@ -379,7 +383,7 @@ describe('helpers', () => { ); const timeline = { savedObjectId: 'savedObject-1', - columns: columnsWithoutEventAction, + columns: columnsWithoutEventAction as ColumnHeaderResult[], version: '1', }; @@ -396,7 +400,7 @@ describe('helpers', () => { ); const timeline = { savedObjectId: 'savedObject-1', - columns: columnsWithoutEventAction, + columns: columnsWithoutEventAction as ColumnHeaderResult[], filters: [ { meta: { @@ -568,7 +572,7 @@ describe('helpers', () => { version: '1', status: TimelineStatusEnum.active, timelineType: TimelineTypeEnum.default, - columns: customColumns, + columns: customColumns as ColumnHeaderResult[], }; const newTimeline = defaultTimelineToTimelineModel( @@ -691,7 +695,7 @@ describe('helpers', () => { }); test('Do not override daterange if TimelineStatus is active', () => { - const { timeline } = formatTimelineResultToModel( + const { timeline } = formatTimelineResponseToModel( omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)), args.duplicate, args.timelineType @@ -744,7 +748,7 @@ describe('helpers', () => { }); test('should not override daterange if TimelineStatus is active', () => { - const { timeline } = formatTimelineResultToModel( + const { timeline } = formatTimelineResponseToModel( omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)), args.duplicate, args.timelineType @@ -818,7 +822,7 @@ describe('helpers', () => { }); test('override daterange if TimelineStatus is immutable', () => { - const { timeline } = formatTimelineResultToModel( + const { timeline } = formatTimelineResponseToModel( omitTypenameInTimeline(getOr({}, 'data.timeline', template)), args.duplicate, args.timelineType diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index 81114901c216d..000a7b226561e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -14,8 +14,8 @@ import { useCallback } from 'react'; import { useDiscoverInTimelineContext } from '../../../common/components/discover_in_timeline/use_discover_in_timeline_context'; import type { ColumnHeaderOptions } from '../../../../common/types/timeline'; import type { - TimelineResult, - SingleTimelineResolveResponse, + TimelineResponse, + ResolvedTimeline, ColumnHeaderResult, FilterTimelineResult, DataProviderResult, @@ -80,7 +80,7 @@ export const isUntitled = ({ title }: OpenTimelineResult): boolean => const omitTypename = (key: string, value: keyof TimelineModel) => key === '__typename' ? undefined : value; -export const omitTypenameInTimeline = (timeline: TimelineResult): TimelineResult => +export const omitTypenameInTimeline = (timeline: TimelineResponse): TimelineResponse => JSON.parse(JSON.stringify(timeline), omitTypename); const parseString = (params: string) => { @@ -164,7 +164,7 @@ const setPinnedEventIds = (duplicate: boolean, pinnedEventIds: string[] | null | : {}; const getTemplateTimelineId = ( - timeline: TimelineResult, + timeline: TimelineResponse, duplicate: boolean, targetTimelineType?: TimelineType ) => { @@ -200,7 +200,7 @@ const convertToDefaultField = ({ and, ...dataProvider }: DataProviderResult) => const getDataProviders = ( duplicate: boolean, - dataProviders: TimelineResult['dataProviders'], + dataProviders: TimelineResponse['dataProviders'], timelineType?: TimelineType ) => { if (duplicate && dataProviders && timelineType === TimelineTypeEnum.default) { @@ -214,7 +214,7 @@ const getDataProviders = ( }; export const getTimelineTitle = ( - timeline: TimelineResult, + timeline: TimelineResponse, duplicate: boolean, timelineType?: TimelineType ) => { @@ -225,7 +225,7 @@ export const getTimelineTitle = ( }; export const getTimelineStatus = ( - timeline: TimelineResult, + timeline: TimelineResponse, duplicate: boolean, timelineType?: TimelineType ) => { @@ -236,7 +236,7 @@ export const getTimelineStatus = ( }; export const defaultTimelineToTimelineModel = ( - timeline: TimelineResult, + timeline: TimelineResponse, duplicate: boolean, timelineType?: TimelineType, unifiedComponentsInTimelineDisabled?: boolean @@ -291,8 +291,8 @@ export const defaultTimelineToTimelineModel = ( ); }; -export const formatTimelineResultToModel = ( - timelineToOpen: TimelineResult, +export const formatTimelineResponseToModel = ( + timelineToOpen: TimelineResponse, duplicate: boolean = false, timelineType?: TimelineType, unifiedComponentsInTimelineDisabled?: boolean @@ -376,12 +376,12 @@ export const useQueryTimelineById = () => { } else { return Promise.resolve(resolveTimeline(timelineId)) .then((result) => { - const data: SingleTimelineResolveResponse['data'] | null = getOr(null, 'data', result); + const data: ResolvedTimeline | null = getOr(null, 'data', result); if (!data) return; const timelineToOpen = omitTypenameInTimeline(data.timeline); - const { timeline, notes } = formatTimelineResultToModel( + const { timeline, notes } = formatTimelineResponseToModel( timelineToOpen, duplicate, timelineType, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts index 1a47c22d0fb61..14ddedf5b9688 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts @@ -10,7 +10,7 @@ import type { IconType } from '@elastic/eui'; import type { TimelineModel } from '../../store/model'; import type { RowRendererId, - SingleTimelineResolveResponse, + ResolvedTimeline, TimelineType, TimelineStatus, TemplateTimelineType, @@ -210,9 +210,9 @@ export interface OpenTimelineProps { } export interface ResolveTimelineConfig { - alias_target_id: SingleTimelineResolveResponse['data']['alias_target_id']; - outcome: SingleTimelineResolveResponse['data']['outcome']; - alias_purpose: SingleTimelineResolveResponse['data']['alias_purpose']; + alias_target_id: ResolvedTimeline['alias_target_id']; + outcome: ResolvedTimeline['outcome']; + alias_purpose: ResolvedTimeline['alias_purpose']; } export interface UpdateTimeline { duplicate: boolean; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx index 7a2b3eca0f74b..9db2a0b8924d9 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx @@ -21,9 +21,9 @@ import type { TimelineType, TimelineStatus, PageInfoTimeline, - TimelineResult, + TimelineResponse, SortTimeline, - GetAllTimelineVariables, + GetTimelinesRequestQuery, } from '../../../../common/api/timeline'; import { TimelineTypeEnum } from '../../../../common/api/timeline'; import { getAllTimelines } from '../api'; @@ -59,7 +59,7 @@ export interface AllTimelinesVariables { export const ALL_TIMELINE_QUERY_ID = 'FETCH_ALL_TIMELINES'; export const getAllTimeline = memoizeOne( - (_variables: string, timelines: TimelineResult[]): OpenTimelineResult[] => + (_variables: string, timelines: TimelineResponse[]): OpenTimelineResult[] => timelines.map((timeline) => ({ created: timeline.created, description: timeline.description, @@ -132,13 +132,15 @@ export const useGetAllTimeline = (): AllTimelinesArgs => { loading: true, })); - const variables: GetAllTimelineVariables = { - onlyUserFavorite, - pageInfo, + const variables: GetTimelinesRequestQuery = { + only_user_favorite: onlyUserFavorite ? 'true' : 'false', + page_size: pageInfo.pageSize.toString(), + page_index: pageInfo.pageIndex.toString(), search, - sort, - status, - timelineType, + sort_field: sort.sortField, + sort_order: sort.sortOrder, + status: status || undefined, + timeline_type: timelineType, }; const getAllTimelineResponse = await getAllTimelines(variables, abortCtrl.signal); const totalCount = getAllTimelineResponse?.totalCount ?? 0; @@ -163,7 +165,7 @@ export const useGetAllTimeline = (): AllTimelinesArgs => { setAllTimelines({ loading: false, totalCount, - timelines: getAllTimeline(JSON.stringify(variables), timelines as TimelineResult[]), + timelines: getAllTimeline(JSON.stringify(variables), timelines as TimelineResponse[]), customTemplateTimelineCount, defaultTimelineCount, elasticTemplateTimelineCount, diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.ts index 155d95c5acef2..fa006293719d5 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.ts @@ -5,34 +5,30 @@ * 2.0. */ -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; -import { pipe } from 'fp-ts/lib/pipeable'; import { isEmpty } from 'lodash'; -import { throwErrors } from '@kbn/cases-plugin/common'; import type { SavedSearch } from '@kbn/saved-search-plugin/common'; import type { - TimelineResponse, - TimelineErrorResponse, - ImportTimelineResultSchema, - AllTimelinesResponse, - SingleTimelineResponse, - SingleTimelineResolveResponse, - GetTimelinesArgs, + CleanDraftTimelinesResponse, + TimelineType, + PatchTimelineResponse, + CreateTimelinesResponse, + CopyTimelineResponse, + GetDraftTimelinesResponse, + GetTimelinesRequestQuery, + SavedTimeline, } from '../../../common/api/timeline'; import { - TimelineResponseType, + ImportTimelineResult, + TimelineErrorResponse, TimelineStatusEnum, - TimelineErrorResponseType, - importTimelineResultSchema, - allTimelinesResponse, PersistFavoriteRouteResponse, - SingleTimelineResponseType, - type TimelineType, TimelineTypeEnum, - ResolvedSingleTimelineResponseType, + GetTimelineResponse, + ResolveTimelineResponse, + GetTimelinesResponse, + PersistTimelineResponse, } from '../../../common/api/timeline'; import { TIMELINE_URL, @@ -48,15 +44,15 @@ import { import { KibanaServices } from '../../common/lib/kibana'; import { ToasterError } from '../../common/components/toasters'; +import { parseOrThrowErrorFactory } from '../../../common/timelines/zod_errors'; import type { ExportDocumentsProps, ImportDataProps, ImportDataResponse, } from '../../detection_engine/rule_management/logic'; -import type { TimelineInput } from '../../../common/search_strategy'; interface RequestPostTimeline { - timeline: TimelineInput; + timeline: SavedTimeline; signal?: AbortSignal; } @@ -68,48 +64,33 @@ interface RequestPatchTimeline extends RequestPostTimeline { type RequestPersistTimeline = RequestPostTimeline & Partial>; const createToasterPlainError = (message: string) => new ToasterError([message]); -const decodeTimelineResponse = (respTimeline?: TimelineResponse | TimelineErrorResponse) => - pipe( - TimelineResponseType.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); -const decodeSingleTimelineResponse = (respTimeline?: SingleTimelineResponse) => - pipe( - SingleTimelineResponseType.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); +const parseOrThrow = parseOrThrowErrorFactory(createToasterPlainError); -const decodeResolvedSingleTimelineResponse = (respTimeline?: SingleTimelineResolveResponse) => - pipe( - ResolvedSingleTimelineResponseType.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); +const decodeTimelineResponse = (respTimeline?: PersistTimelineResponse | TimelineErrorResponse) => + parseOrThrow(PersistTimelineResponse)(respTimeline); -const decodeAllTimelinesResponse = (respTimeline: AllTimelinesResponse) => - pipe( - allTimelinesResponse.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); +const decodeSingleTimelineResponse = (respTimeline?: GetTimelineResponse) => + parseOrThrow(GetTimelineResponse)(respTimeline); + +const decodeResolvedSingleTimelineResponse = (respTimeline?: ResolveTimelineResponse) => + parseOrThrow(ResolveTimelineResponse)(respTimeline); + +const decodeGetTimelinesResponse = (respTimeline: GetTimelinesResponse) => + parseOrThrow(GetTimelinesResponse)(respTimeline); const decodeTimelineErrorResponse = (respTimeline?: TimelineErrorResponse) => - pipe( - TimelineErrorResponseType.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); + parseOrThrow(TimelineErrorResponse)(respTimeline); -const decodePrepackedTimelineResponse = (respTimeline?: ImportTimelineResultSchema) => - pipe( - importTimelineResultSchema.decode(respTimeline), - fold(throwErrors(createToasterPlainError), identity) - ); +const decodePrepackedTimelineResponse = (respTimeline?: ImportTimelineResult) => + parseOrThrow(ImportTimelineResult)(respTimeline); const decodeResponseFavoriteTimeline = (respTimeline?: PersistFavoriteRouteResponse) => - PersistFavoriteRouteResponse.parse(respTimeline); + parseOrThrow(PersistFavoriteRouteResponse)(respTimeline); const postTimeline = async ({ timeline, -}: RequestPostTimeline): Promise => { +}: RequestPostTimeline): Promise => { let requestBody; try { requestBody = JSON.stringify({ timeline }); @@ -117,7 +98,7 @@ const postTimeline = async ({ return Promise.reject(new Error(`Failed to stringify query: ${JSON.stringify(err)}`)); } - const response = await KibanaServices.get().http.post(TIMELINE_URL, { + const response = await KibanaServices.get().http.post(TIMELINE_URL, { method: 'POST', body: requestBody, version: '2023-10-31', @@ -131,7 +112,7 @@ const patchTimeline = async ({ timeline, version, savedSearch, -}: RequestPatchTimeline): Promise => { +}: RequestPatchTimeline): Promise => { let response = null; let requestBody = null; try { @@ -153,7 +134,7 @@ const patchTimeline = async ({ } try { - response = await KibanaServices.get().http.patch(TIMELINE_URL, { + response = await KibanaServices.get().http.patch(TIMELINE_URL, { method: 'PATCH', body: requestBody, version: '2023-10-31', @@ -175,7 +156,7 @@ export const copyTimeline = async ({ timelineId, timeline, savedSearch, -}: RequestPersistTimeline): Promise => { +}: RequestPersistTimeline): Promise => { let response = null; let requestBody = null; let newSavedSearchId = null; @@ -205,7 +186,7 @@ export const copyTimeline = async ({ } try { - response = await KibanaServices.get().http.post(TIMELINE_COPY_URL, { + response = await KibanaServices.get().http.post(TIMELINE_COPY_URL, { method: 'POST', body: requestBody, version: '1', @@ -224,10 +205,10 @@ export const persistTimeline = async ({ timeline, version, savedSearch, -}: RequestPersistTimeline): Promise => { +}: RequestPersistTimeline): Promise => { try { if (isEmpty(timelineId) && timeline.status === TimelineStatusEnum.draft && timeline) { - const temp: TimelineResponse | TimelineErrorResponse = await cleanDraftTimeline({ + const temp: CleanDraftTimelinesResponse | TimelineErrorResponse = await cleanDraftTimeline({ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion timelineType: timeline.timelineType!, templateTimelineId: timeline.templateTimelineId ?? undefined, @@ -330,13 +311,16 @@ export const getDraftTimeline = async ({ timelineType, }: { timelineType: TimelineType; -}): Promise => { - const response = await KibanaServices.get().http.get(TIMELINE_DRAFT_URL, { - query: { - timelineType, - }, - version: '2023-10-31', - }); +}): Promise => { + const response = await KibanaServices.get().http.get( + TIMELINE_DRAFT_URL, + { + query: { + timelineType, + }, + version: '2023-10-31', + } + ); return decodeTimelineResponse(response); }; @@ -349,7 +333,7 @@ export const cleanDraftTimeline = async ({ timelineType: TimelineType; templateTimelineId?: string; templateTimelineVersion?: number; -}): Promise => { +}): Promise => { let requestBody; const templateTimelineInfo = timelineType === TimelineTypeEnum.template @@ -366,16 +350,19 @@ export const cleanDraftTimeline = async ({ } catch (err) { return Promise.reject(new Error(`Failed to stringify query: ${JSON.stringify(err)}`)); } - const response = await KibanaServices.get().http.post(TIMELINE_DRAFT_URL, { - body: requestBody, - version: '2023-10-31', - }); + const response = await KibanaServices.get().http.post( + TIMELINE_DRAFT_URL, + { + body: requestBody, + version: '2023-10-31', + } + ); return decodeTimelineResponse(response); }; -export const installPrepackedTimelines = async (): Promise => { - const response = await KibanaServices.get().http.post( +export const installPrepackedTimelines = async (): Promise => { + const response = await KibanaServices.get().http.post( TIMELINE_PREPACKAGED_URL, { version: '2023-10-31', @@ -386,7 +373,7 @@ export const installPrepackedTimelines = async (): Promise { - const response = await KibanaServices.get().http.get(TIMELINE_URL, { + const response = await KibanaServices.get().http.get(TIMELINE_URL, { query: { id, }, @@ -397,7 +384,7 @@ export const getTimeline = async (id: string) => { }; export const resolveTimeline = async (id: string) => { - const response = await KibanaServices.get().http.get( + const response = await KibanaServices.get().http.get( TIMELINE_RESOLVE_URL, { query: { @@ -411,7 +398,7 @@ export const resolveTimeline = async (id: string) => { }; export const getTimelineTemplate = async (templateTimelineId: string) => { - const response = await KibanaServices.get().http.get(TIMELINE_URL, { + const response = await KibanaServices.get().http.get(TIMELINE_URL, { query: { template_timeline_id: templateTimelineId, }, @@ -421,24 +408,18 @@ export const getTimelineTemplate = async (templateTimelineId: string) => { return decodeSingleTimelineResponse(response); }; -export const getAllTimelines = async (args: GetTimelinesArgs, abortSignal: AbortSignal) => { - const response = await KibanaServices.get().http.fetch(TIMELINES_URL, { +export const getAllTimelines = async ( + query: GetTimelinesRequestQuery, + abortSignal: AbortSignal +) => { + const response = await KibanaServices.get().http.fetch(TIMELINES_URL, { method: 'GET', - query: { - ...(args.onlyUserFavorite ? { only_user_favorite: args.onlyUserFavorite } : {}), - ...(args?.pageInfo?.pageSize ? { page_size: args.pageInfo.pageSize } : {}), - ...(args?.pageInfo?.pageIndex ? { page_index: args.pageInfo.pageIndex } : {}), - ...(args.search ? { search: args.search } : {}), - ...(args?.sort?.sortField ? { sort_field: args?.sort?.sortField } : {}), - ...(args?.sort?.sortOrder ? { sort_order: args?.sort?.sortOrder } : {}), - ...(args.status ? { status: args.status } : {}), - ...(args.timelineType ? { timeline_type: args.timelineType } : {}), - }, + query, signal: abortSignal, version: '2023-10-31', }); - return decodeAllTimelinesResponse(response); + return decodeGetTimelinesResponse(response); }; export const persistFavorite = async ({ diff --git a/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts b/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts index 17d0050488f5b..fef5fa22f0bfb 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts @@ -6,10 +6,10 @@ */ import { TableId } from '@kbn/securitysolution-data-table'; -import type { TimelineResult } from '../../../common/api/timeline'; +import type { TimelineResponse } from '../../../common/api/timeline'; import { DEFAULT_ALERTS_INDEX } from '../../../common/constants'; -export const getTimelineQueryTypes = (timeline: TimelineResult) => ({ +export const getTimelineQueryTypes = (timeline: TimelineResponse) => ({ hasQuery: (timeline.kqlQuery != null && timeline.kqlQuery.filterQuery != null && 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 4c303b1414515..67374a66019db 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 @@ -66,6 +66,7 @@ describe('Timeline pinned event middleware', () => { data: { persistPinnedEventOnTimeline: { code: 200, + eventId: testEventId, }, }, }); 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 8461ed3c2fc17..38e00af3f5f8e 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 @@ -74,7 +74,7 @@ export const addPinnedEventToTimelineMiddleware: (kibana: CoreStart) => Middlewa const currentTimeline = selectTimelineById(store.getState(), action.payload.id); // The response is null or empty in case we unpinned an event. // In that case we want to remove the locally pinned event. - if (!response || !('code' in response)) { + if (!response || !('eventId' in response)) { return store.dispatch( updateTimeline({ id: action.payload.id, diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts index 6051c9c1bb4e5..3c8bcf4b55f58 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts @@ -167,7 +167,7 @@ describe('Timeline save middleware', () => { }); it('should show an error message when the call is unauthorized', async () => { - (persistTimeline as jest.Mock).mockResolvedValue({ data: { persistTimeline: { code: 403 } } }); + (persistTimeline as jest.Mock).mockResolvedValue({ status_code: 403 }); await store.dispatch(saveTimeline({ id: TimelineId.test, saveAsNew: false })); expect(refreshTimelines as unknown as jest.Mock).not.toHaveBeenCalled(); @@ -175,7 +175,7 @@ describe('Timeline save middleware', () => { }); describe('#convertTimelineAsInput ', () => { - test('should return a TimelineInput instead of TimelineModel ', () => { + test('should return a SavedTimeline instead of TimelineModel ', () => { const columns: TimelineModel['columns'] = [ { columnHeaderType: 'not-filtered', diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts index dcbbc5cadb6d0..58e8aced4470b 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts @@ -34,8 +34,11 @@ import { selectTimelineById } from '../selectors'; import * as i18n from '../../pages/translations'; import type { inputsModel } from '../../../common/store/inputs'; import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; -import type { TimelineErrorResponse, TimelineResponse } from '../../../../common/api/timeline'; -import type { TimelineInput } from '../../../../common/search_strategy'; +import type { + TimelineErrorResponse, + PersistTimelineResponse, + SavedTimeline, +} from '../../../../common/api/timeline'; import type { TimelineModel } from '../model'; import type { ColumnHeaderOptions } from '../../../../common/types/timeline'; import { refreshTimelines } from './helpers'; @@ -83,6 +86,9 @@ export const saveTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State if (isTimelineErrorResponse(result)) { const error = getErrorFromResponse(result); switch (error?.errorCode) { + case 403: + store.dispatch(showCallOutUnauthorizedMsg()); + break; // conflict case 409: kibana.notifications.toasts.addDanger({ @@ -108,11 +114,6 @@ export const saveTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State return; } - if (response && response.code === 403) { - store.dispatch(showCallOutUnauthorizedMsg()); - return; - } - refreshTimelines(store.getState()); store.dispatch( @@ -155,7 +156,7 @@ export const saveTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State return ret; }; -const timelineInput: TimelineInput = { +const timelineInput: SavedTimeline = { columns: null, dataProviders: null, dataViewId: null, @@ -181,8 +182,8 @@ const timelineInput: TimelineInput = { export const convertTimelineAsInput = ( timeline: TimelineModel, timelineTimeRange: inputsModel.TimeRange -): TimelineInput => - Object.keys(timelineInput).reduce((acc, key) => { +): SavedTimeline => + Object.keys(timelineInput).reduce((acc, key) => { if (has(key, timeline)) { if (key === 'kqlQuery') { return set(`${key}.filterQuery`, get(`${key}.filterQuery`, timeline), acc); @@ -270,7 +271,7 @@ const convertToString = (obj: unknown) => { } }; -type PossibleResponse = TimelineResponse | TimelineErrorResponse; +type PossibleResponse = PersistTimelineResponse | TimelineErrorResponse; function isTimelineErrorResponse(response: PossibleResponse): response is TimelineErrorResponse { return response && ('status_code' in response || 'statusCode' in response); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts index 6b88ec1923d0d..3713176e919c5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts @@ -6,8 +6,7 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { validate } from '@kbn/securitysolution-io-ts-utils'; -import { checkTimelineStatusRt } from '../../../../../../common/api/timeline'; +import { InstallPrepackedTimelinesRequestBody } from '../../../../../../common/api/timeline'; import { buildSiemResponse } from '../../../routes/utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; @@ -69,10 +68,8 @@ export const getPrebuiltRulesAndTimelinesStatusRoute = (router: SecuritySolution const frameworkRequest = await buildFrameworkRequest(context, request); const prebuiltTimelineStatus = await checkTimelinesStatus(frameworkRequest); - const [validatedPrebuiltTimelineStatus] = validate( - prebuiltTimelineStatus, - checkTimelineStatusRt - ); + const validatedPrebuiltTimelineStatus = + InstallPrepackedTimelinesRequestBody.parse(prebuiltTimelineStatus); const responseBody: ReadPrebuiltRulesAndTimelinesStatusResponse = { rules_custom_installed: customRules.total, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/perform_timelines_installation.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/perform_timelines_installation.ts index 101883e8ccd9b..857c7a6b95814 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/perform_timelines_installation.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/perform_timelines_installation.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; -import { importTimelineResultSchema } from '../../../../../common/api/timeline'; +import { stringifyZodError } from '@kbn/zod-helpers'; +import { ImportTimelineResult } from '../../../../../common/api/timeline'; import type { SecuritySolutionApiRequestHandlerContext } from '../../../../types'; import { installPrepackagedTimelines } from '../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; @@ -18,10 +18,10 @@ export const performTimelinesInstallation = async ( securitySolutionContext.getFrameworkRequest(), true ); - const [result, error] = validate(timeline, importTimelineResultSchema); + const parsed = ImportTimelineResult.safeParse(timeline); return { - result, - error, + result: parsed.data, + error: parsed.error && stringifyZodError(parsed.error), }; }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts index eb4649f941d11..70187dc9bf40a 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts @@ -6,7 +6,6 @@ */ import path, { join, resolve } from 'path'; -import type * as rt from 'io-ts'; import { TIMELINE_DRAFT_URL, @@ -17,9 +16,9 @@ import { } from '../../../../common/constants'; import type { SavedTimeline, - patchTimelineSchema, - createTimelineSchema, - GetTimelineQuery, + PatchTimelineRequestBody, + CreateTimelinesRequestBody, + GetTimelineRequestQuery, } from '../../../../common/api/timeline'; import { type TimelineType, @@ -135,14 +134,14 @@ export const updateTemplateTimelineWithTimelineId = { version: 'WzEyMjUsMV0=', }; -export const getCreateTimelinesRequest = (mockBody: rt.TypeOf) => +export const getCreateTimelinesRequest = (mockBody: CreateTimelinesRequestBody) => requestMock.create({ method: 'post', path: TIMELINE_URL, body: mockBody, }); -export const getUpdateTimelinesRequest = (mockBody: rt.TypeOf) => +export const getUpdateTimelinesRequest = (mockBody: PatchTimelineRequestBody) => requestMock.create({ method: 'patch', path: TIMELINE_URL, @@ -167,7 +166,7 @@ export const cleanDraftTimelinesRequest = (timelineType: TimelineType) => }, }); -export const getTimelineRequest = (query?: GetTimelineQuery) => +export const getTimelineRequest = (query?: GetTimelineRequestQuery) => requestMock.create({ method: 'get', path: TIMELINE_URL, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts index ea875a71416c1..932e6f3ce904d 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ResolvedTimelineWithOutcomeSavedObject } from '../../../../common/api/timeline'; +import type { ResolvedTimeline } from '../../../../common/api/timeline'; import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; export const mockResolvedSavedObject = { @@ -117,7 +117,7 @@ export const mockPopulatedTimeline = { pinnedEventsSaveObject: [], }; -export const mockResolveTimelineResponse: ResolvedTimelineWithOutcomeSavedObject = { +export const mockResolveTimelineResponse: ResolvedTimeline = { timeline: mockPopulatedTimeline, outcome: 'aliasMatch', alias_target_id: 'new-saved-object-id', 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 387720b4a3b4f..6515817f28e11 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 @@ -6,10 +6,10 @@ */ import { v4 as uuidv4 } from 'uuid'; +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import type { ConfigType } from '../../../../..'; import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { TIMELINE_DRAFT_URL } from '../../../../../../common/constants'; @@ -21,12 +21,13 @@ import { persistTimeline, } from '../../../saved_object/timelines'; import { draftTimelineDefaults } from '../../../utils/default_timeline'; +import type { CleanDraftTimelinesResponse } from '../../../../../../common/api/timeline'; import { CleanDraftTimelinesRequestBody, TimelineTypeEnum, } from '../../../../../../common/api/timeline'; -export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .post({ path: TIMELINE_DRAFT_URL, @@ -42,7 +43,7 @@ export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _ }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { const frameworkRequest = await buildFrameworkRequest(context, request); const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts index 6619e4a0eb18b..1ba3167cdefae 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts @@ -5,19 +5,22 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import type { ConfigType } from '../../../../..'; import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { TIMELINE_DRAFT_URL } from '../../../../../../common/constants'; import { buildFrameworkRequest } from '../../../utils/common'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; import { getDraftTimeline, persistTimeline } from '../../../saved_object/timelines'; import { draftTimelineDefaults } from '../../../utils/default_timeline'; -import { getDraftTimelineSchema } from '../../../../../../common/api/timeline'; +import { + GetDraftTimelinesRequestQuery, + type GetDraftTimelinesResponse, +} from '../../../../../../common/api/timeline'; -export const getDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const getDraftTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: TIMELINE_DRAFT_URL, @@ -29,11 +32,11 @@ export const getDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _: .addVersion( { validate: { - request: { query: buildRouteValidationWithExcess(getDraftTimelineSchema) }, + request: { query: buildRouteValidationWithZod(GetDraftTimelinesRequestQuery) }, }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { const frameworkRequest = await buildFrameworkRequest(context, request); const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/index.ts index 6b44496b6c3c4..905e28872a5a4 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/index.ts @@ -28,25 +28,25 @@ import { persistNoteRoute, deleteNoteRoute, getNotesRoute } from './notes'; import { persistPinnedEventRoute } from './pinned_events'; export function registerTimelineRoutes(router: SecuritySolutionPluginRouter, config: ConfigType) { - createTimelinesRoute(router, config); - patchTimelinesRoute(router, config); + createTimelinesRoute(router); + patchTimelinesRoute(router); importTimelinesRoute(router, config); exportTimelinesRoute(router, config); - getDraftTimelinesRoute(router, config); - getTimelineRoute(router, config); - resolveTimelineRoute(router, config); - getTimelinesRoute(router, config); - cleanDraftTimelinesRoute(router, config); - deleteTimelinesRoute(router, config); - persistFavoriteRoute(router, config); - copyTimelineRoute(router, config); + getDraftTimelinesRoute(router); + getTimelineRoute(router); + resolveTimelineRoute(router); + getTimelinesRoute(router); + cleanDraftTimelinesRoute(router); + deleteTimelinesRoute(router); + persistFavoriteRoute(router); + copyTimelineRoute(router); installPrepackedTimelinesRoute(router, config); - persistNoteRoute(router, config); - deleteNoteRoute(router, config); - getNotesRoute(router, config); + persistNoteRoute(router); + deleteNoteRoute(router); + getNotesRoute(router); - persistPinnedEventRoute(router, config); + persistPinnedEventRoute(router); } diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts index 92be926453403..9e6aeb5473fc2 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts @@ -5,21 +5,20 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { NOTE_URL } from '../../../../../common/constants'; -import type { ConfigType } from '../../../..'; - import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../utils/common'; import { DeleteNoteRequestBody, type DeleteNoteResponse } from '../../../../../common/api/timeline'; import { deleteNote } from '../../saved_object/notes'; -export const deleteNoteRoute = (router: SecuritySolutionPluginRouter, config: ConfigType) => { +export const deleteNoteRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .delete({ path: NOTE_URL, @@ -35,7 +34,7 @@ export const deleteNoteRoute = (router: SecuritySolutionPluginRouter, config: Co }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { const siemResponse = buildSiemResponse(response); try { @@ -55,9 +54,8 @@ export const deleteNoteRoute = (router: SecuritySolutionPluginRouter, config: Co noteIds, }); - const body: DeleteNoteResponse = { data: {} }; return response.ok({ - body, + body: { data: {} }, }); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts index 9cc8435d6aae0..920a7ef763dd5 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts @@ -5,21 +5,20 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { NOTE_URL } from '../../../../../common/constants'; -import type { ConfigType } from '../../../..'; - import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../utils/common'; import { getAllSavedNote, MAX_UNASSOCIATED_NOTES } from '../../saved_object/notes'; import { noteSavedObjectType } from '../../saved_object_mappings/notes'; import { GetNotesRequestQuery, type GetNotesResponse } from '../../../../../common/api/timeline'; -export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const getNotesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: NOTE_URL, @@ -35,7 +34,7 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigTyp }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { try { const queryParams = request.query; const frameworkRequest = await buildFrameworkRequest(context, request); @@ -60,8 +59,7 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigTyp perPage: MAX_UNASSOCIATED_NOTES, }; const res = await getAllSavedNote(frameworkRequest, options); - const body: GetNotesResponse = res ?? {}; - return response.ok({ body }); + return response.ok({ body: res ?? {} }); } } else { const perPage = queryParams?.perPage ? parseInt(queryParams.perPage, 10) : 10; 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 7ee0dc886a787..2e825b4ff3a15 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 @@ -5,14 +5,13 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { NOTE_URL } from '../../../../../common/constants'; -import type { ConfigType } from '../../../..'; - import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../utils/common'; @@ -22,7 +21,7 @@ import { } from '../../../../../common/api/timeline'; import { persistNote } from '../../saved_object/notes'; -export const persistNoteRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const persistNoteRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .patch({ path: NOTE_URL, @@ -38,7 +37,7 @@ export const persistNoteRoute = (router: SecuritySolutionPluginRouter, _: Config }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { const siemResponse = buildSiemResponse(response); try { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts index c1e245cda40fb..74db9e58d904b 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; @@ -12,8 +13,6 @@ import type { SecuritySolutionPluginRouter } from '../../../../types'; import { PINNED_EVENT_URL } from '../../../../../common/constants'; -import type { ConfigType } from '../../../..'; - import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../utils/common'; @@ -23,10 +22,7 @@ import { } from '../../../../../common/api/timeline'; import { persistPinnedEventOnTimeline } from '../../saved_object/pinned_events'; -export const persistPinnedEventRoute = ( - router: SecuritySolutionPluginRouter, - config: ConfigType -) => { +export const persistPinnedEventRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .patch({ path: PINNED_EVENT_URL, @@ -42,7 +38,11 @@ export const persistPinnedEventRoute = ( }, version: '2023-10-31', }, - async (context, request, response) => { + async ( + context, + request, + response + ): Promise> => { const siemResponse = buildSiemResponse(response); try { @@ -58,12 +58,10 @@ export const persistPinnedEventRoute = ( timelineId ); - const body: PersistPinnedEventRouteResponse = { - data: { persistPinnedEventOnTimeline: res }, - }; - return response.ok({ - body, + body: { + data: { persistPinnedEventOnTimeline: res }, + }, }); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts index 7a4e7a41a1ee7..6b8124f16aa91 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts @@ -20,7 +20,6 @@ import { import * as helpers from './helpers'; import { importTimelines } from '../../timelines/import_timelines/helpers'; import { buildFrameworkRequest } from '../../../utils/common'; -import type { ImportTimelineResultSchema } from '../../../../../../common/api/timeline'; jest.mock('../../timelines/import_timelines/helpers'); @@ -231,9 +230,8 @@ describe('installPrepackagedTimelines', () => { ); expect( - (result as ImportTimelineResultSchema).errors[0].error.message.includes( - 'read prepackaged timelines error:' - ) + 'errors' in result && + result.errors?.[0].error?.message?.includes('read prepackaged timelines error:') ).toBeTruthy(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts index dee31d479e2da..f6f43435e5641 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts @@ -8,7 +8,7 @@ import path, { join, resolve } from 'path'; import { Readable } from 'stream'; -import type { ImportTimelineResultSchema } from '../../../../../../common/api/timeline'; +import type { ImportTimelineResult } from '../../../../../../common/api/timeline'; import type { FrameworkRequest } from '../../../../framework'; @@ -22,7 +22,7 @@ export const installPrepackagedTimelines = async ( isImmutable: boolean, filePath?: string, fileName?: string -): Promise => { +): Promise => { let readStream; const dir = resolve( join( @@ -47,7 +47,7 @@ export const installPrepackagedTimelines = async ( ], }; } - return loadData(readStream, (docs: T) => + return loadData(readStream, (docs: T) => docs instanceof Readable ? importTimelines(docs, maxTimelineImportExportSize, frameworkRequest, isImmutable) : Promise.reject(new Error(`read prepackaged timelines error`)) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts index 0b150b4e47f56..b1a6e2f781f45 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts @@ -6,14 +6,17 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { validate } from '@kbn/securitysolution-io-ts-utils'; -import { checkTimelineStatusRt } from '../../../../../../common/api/timeline'; +import type { IKibanaResponse } from '@kbn/core-http-server'; + import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_PREPACKAGED_URL } from '../../../../../../common/constants'; import type { ConfigType } from '../../../../../config'; - +import { + InstallPrepackedTimelinesRequestBody, + type InstallPrepackedTimelinesResponse, +} from '../../../../../../common/api/timeline'; import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { installPrepackagedTimelines } from './helpers'; @@ -45,23 +48,24 @@ export const installPrepackedTimelinesRoute = ( validate: {}, version: '2023-10-31', }, - async (context, request, response) => { + async ( + context, + request, + response + ): Promise> => { try { const frameworkRequest = await buildFrameworkRequest(context, request); const prepackagedTimelineStatus = await checkTimelinesStatus(frameworkRequest); - const [validatedprepackagedTimelineStatus, prepackagedTimelineStatusError] = validate( - prepackagedTimelineStatus, - checkTimelineStatusRt - ); - if (prepackagedTimelineStatusError != null) { - throw prepackagedTimelineStatusError; + const installResult = + InstallPrepackedTimelinesRequestBody.safeParse(prepackagedTimelineStatus); + + if (installResult.error) { + throw installResult.error; } - const timelinesToInstalled = - validatedprepackagedTimelineStatus?.timelinesToInstall.length ?? 0; - const timelinesNotUpdated = - validatedprepackagedTimelineStatus?.timelinesToUpdate.length ?? 0; + const timelinesToInstalled = installResult.data.timelinesToInstall.length ?? 0; + const timelinesNotUpdated = installResult.data.timelinesToUpdate.length ?? 0; let res = null; if (timelinesToInstalled > 0 || timelinesNotUpdated > 0) { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts index 5bdb4e0f93d67..e795ec89dd926 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts @@ -5,10 +5,13 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; -import type { ConfigType } from '../../../../..'; -import { copyTimelineSchema } from '../../../../../../common/api/timeline'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { + CopyTimelineRequestBody, + type CopyTimelineResponse, +} from '../../../../../../common/api/timeline'; import { copyTimeline } from '../../../saved_object/timelines'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_COPY_URL } from '../../../../../../common/constants'; @@ -16,7 +19,7 @@ import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; -export const copyTimelineRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const copyTimelineRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .post({ path: TIMELINE_COPY_URL, @@ -29,17 +32,16 @@ export const copyTimelineRoute = (router: SecuritySolutionPluginRouter, _: Confi { version: '1', validate: { - request: { body: buildRouteValidationWithExcess(copyTimelineSchema) }, + request: { body: buildRouteValidationWithZod(CopyTimelineRequestBody) }, }, }, - async (context, request, response) => { + async (context, request, response): Promise> => { const siemResponse = buildSiemResponse(response); try { const frameworkRequest = await buildFrameworkRequest(context, request); const { timeline, timelineIdToCopy } = request.body; const copiedTimeline = await copyTimeline(frameworkRequest, timeline, timelineIdToCopy); - return response.ok({ body: { data: { persistTimeline: copiedTimeline } }, }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts index 1428f09d39560..ac05fe3f880e9 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts @@ -10,8 +10,9 @@ import { isEmpty } from 'lodash/fp'; import moment from 'moment'; import { timeline as timelineLib, pinnedEvent as pinnedEventLib } from '../../../saved_object'; import type { FrameworkRequest } from '../../../../framework'; -import type { ResponseTimeline, SavedTimeline, Note } from '../../../../../../common/api/timeline'; +import type { SavedTimeline, Note } from '../../../../../../common/api/timeline'; import { persistNotes } from '../../../saved_object/notes/persist_notes'; +import type { InternalTimelineResponse } from '../../../saved_object/timelines'; interface CreateTimelineProps { frameworkRequest: FrameworkRequest; @@ -21,7 +22,7 @@ interface CreateTimelineProps { overrideNotesOwner?: boolean; pinnedEventIds?: string[] | null; notes?: Note[]; - existingNoteIds?: string[]; + existingNoteIds?: string[] | null; isImmutable?: boolean; } @@ -40,7 +41,7 @@ export const createTimelines = async ({ existingNoteIds = [], isImmutable, overrideNotesOwner = true, -}: CreateTimelineProps): Promise => { +}: CreateTimelineProps): Promise => { const timerangeStart = isImmutable ? moment().subtract(24, 'hours').toISOString() : timeline.dateRange?.start; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts index 0fcf286661b96..95fb09fb28e56 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts @@ -7,16 +7,13 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import type { IKibanaResponse } from '@kbn/core/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_URL } from '../../../../../../common/constants'; -import type { ConfigType } from '../../../../..'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; -import { createTimelineSchema } from '../../../../../../common/api/timeline'; import { buildFrameworkRequest, CompareTimelinesStatus, @@ -24,11 +21,14 @@ import { } from '../../../utils/common'; import { DEFAULT_ERROR } from '../../../utils/failure_cases'; import { createTimelines } from './helpers'; -import type { CreateTimelinesResponse } from '../../../../../../common/api/timeline'; +import { + CreateTimelinesRequestBody, + type CreateTimelinesResponse, +} from '../../../../../../common/api/timeline'; export * from './helpers'; -export const createTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const createTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .post({ path: TIMELINE_URL, @@ -42,7 +42,7 @@ export const createTimelinesRoute = (router: SecuritySolutionPluginRouter, _: Co version: '2023-10-31', validate: { request: { - body: buildRouteValidationWithExcess(createTimelineSchema), + body: buildRouteValidationWithZod(CreateTimelinesRequestBody), }, }, }, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts index a11b16c96fb1f..8dd476c9f4e44 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts @@ -5,9 +5,9 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import type { ConfigType } from '../../../../..'; import { DeleteTimelinesRequestBody, type DeleteTimelinesResponse, @@ -19,7 +19,7 @@ import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; import { deleteTimeline } from '../../../saved_object/timelines'; -export const deleteTimelinesRoute = (router: SecuritySolutionPluginRouter, config: ConfigType) => { +export const deleteTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .delete({ path: TIMELINE_URL, @@ -35,7 +35,7 @@ export const deleteTimelinesRoute = (router: SecuritySolutionPluginRouter, confi request: { body: buildRouteValidationWithZod(DeleteTimelinesRequestBody) }, }, }, - async (context, request, response) => { + async (context, request, response): Promise> => { const siemResponse = buildSiemResponse(response); try { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts index 4974e6bd0edda..6e4f3d84da1ff 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts @@ -9,11 +9,11 @@ import { omit } from 'lodash/fp'; import { transformDataToNdjson } from '@kbn/securitysolution-utils'; import type { - ExportedTimelines, ExportedNotes, ExportTimelineNotFoundError, Note, PinnedEvent, + TimelineResponse, } from '../../../../../../common/api/timeline'; import type { FrameworkRequest } from '../../../../framework'; @@ -45,7 +45,7 @@ const getPinnedEventsIdsByTimelineId = (currentPinnedEvents: PinnedEvent[]): str const getTimelinesFromObjects = async ( request: FrameworkRequest, ids?: string[] | null -): Promise> => { +): Promise> => { const { timelines, errors } = await getSelectedTimelines(request, ids); const exportedIds = timelines.map((t) => t.savedObjectId); @@ -65,7 +65,7 @@ const getTimelinesFromObjects = async ( [] ); - const myResponse = exportedIds.reduce((acc, timelineId) => { + const myResponse = exportedIds.reduce((acc, timelineId) => { const myTimeline = timelines.find((t) => t.savedObjectId === timelineId); if (myTimeline != null) { const timelineNotes = myNotes.filter((n) => n.timelineId === timelineId); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts index 78df97fa441be..781da93355442 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - serverMock, - requestContextMock, - createMockConfig, -} from '../../../../detection_engine/routes/__mocks__'; +import { serverMock, requestContextMock } from '../../../../detection_engine/routes/__mocks__'; import { getTimelineOrNull, getTimelineTemplateOrNull } from '../../../saved_object/timelines'; import { getTimelineRequest } from '../../../__mocks__/request_responses'; @@ -33,7 +29,7 @@ describe('get timeline', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - getTimelineRoute(server.router, createMockConfig()); + getTimelineRoute(server.router); }); test('should call getTimelineTemplateOrNull if templateTimelineId is given', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts index 321e6aafe4903..870955f7e8691 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts @@ -5,25 +5,24 @@ * 2.0. */ +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_URL } from '../../../../../../common/constants'; -import type { ConfigType } from '../../../../..'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; -import { getTimelineQuerySchema } from '../../../../../../common/api/timeline'; -import { getTimelineTemplateOrNull, getTimelineOrNull } from '../../../saved_object/timelines'; -import type { - TimelineSavedObject, - ResolvedTimelineWithOutcomeSavedObject, +import { + GetTimelineRequestQuery, + type GetTimelineResponse, } from '../../../../../../common/api/timeline'; +import { getTimelineTemplateOrNull, getTimelineOrNull } from '../../../saved_object/timelines'; +import type { ResolvedTimeline, TimelineResponse } from '../../../../../../common/api/timeline'; -export const getTimelineRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const getTimelineRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: TIMELINE_URL, @@ -36,16 +35,16 @@ export const getTimelineRoute = (router: SecuritySolutionPluginRouter, _: Config { version: '2023-10-31', validate: { - request: { query: buildRouteValidationWithExcess(getTimelineQuerySchema) }, + request: { query: buildRouteValidationWithZod(GetTimelineRequestQuery) }, }, }, - async (context, request, response) => { + async (context, request, response): Promise> => { try { const frameworkRequest = await buildFrameworkRequest(context, request); const query = request.query ?? {}; const { template_timeline_id: templateTimelineId, id } = query; - let res: TimelineSavedObject | ResolvedTimelineWithOutcomeSavedObject | null = null; + let res: TimelineResponse | ResolvedTimeline | null = null; if (templateTimelineId != null && id == null) { res = await getTimelineTemplateOrNull(frameworkRequest, templateTimelineId); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts index b006b77c9a595..753ac1eb15aea 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - serverMock, - requestContextMock, - createMockConfig, -} from '../../../../detection_engine/routes/__mocks__'; +import { serverMock, requestContextMock } from '../../../../detection_engine/routes/__mocks__'; import { getAllTimeline } from '../../../saved_object/timelines'; import { getTimelineRequest } from '../../../__mocks__/request_responses'; import { getTimelinesRoute } from '.'; @@ -29,7 +25,7 @@ describe('get all timelines', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - getTimelinesRoute(server.router, createMockConfig()); + getTimelinesRoute(server.router); }); test('should get the total count', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts index 97d9bf51a0876..52995efcf4be1 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts @@ -5,24 +5,23 @@ * 2.0. */ -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; +import type { IKibanaResponse } from '@kbn/core-http-server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINES_URL } from '../../../../../../common/constants'; -import type { ConfigType } from '../../../../..'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; -import { CustomHttpRequestError } from '../../../../../utils/custom_http_request_error'; -import { buildFrameworkRequest, escapeHatch, throwErrors } from '../../../utils/common'; +import { buildFrameworkRequest } from '../../../utils/common'; import { getAllTimeline } from '../../../saved_object/timelines'; -import { getTimelinesQuerySchema } from '../../../../../../common/api/timeline'; +import { + GetTimelinesRequestQuery, + type GetTimelinesResponse, +} from '../../../../../../common/api/timeline'; -export const getTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const getTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: TIMELINES_URL, @@ -34,27 +33,23 @@ export const getTimelinesRoute = (router: SecuritySolutionPluginRouter, _: Confi .addVersion( { validate: { - request: { query: escapeHatch }, + request: { query: buildRouteValidationWithZod(GetTimelinesRequestQuery) }, }, version: '2023-10-31', }, - async (context, request, response) => { - const customHttpRequestError = (message: string) => - new CustomHttpRequestError(message, 400); + async (context, request, response): Promise> => { try { const frameworkRequest = await buildFrameworkRequest(context, request); - const queryParams = pipe( - getTimelinesQuerySchema.decode(request.query), - fold(throwErrors(customHttpRequestError), identity) - ); - const onlyUserFavorite = queryParams?.only_user_favorite === 'true' ? true : false; - const pageSize = queryParams?.page_size ? parseInt(queryParams.page_size, 10) : null; - const pageIndex = queryParams?.page_index ? parseInt(queryParams.page_index, 10) : null; - const search = queryParams?.search ?? null; - const sortField = queryParams?.sort_field ?? null; - const sortOrder = queryParams?.sort_order ?? null; - const status = queryParams?.status ?? null; - const timelineType = queryParams?.timeline_type ?? null; + const onlyUserFavorite = request.query?.only_user_favorite === 'true'; + const pageSize = request.query?.page_size ? parseInt(request.query.page_size, 10) : null; + const pageIndex = request.query?.page_index + ? parseInt(request.query.page_index, 10) + : null; + const search = request.query?.search ?? null; + const sortField = request.query?.sort_field ?? null; + const sortOrder = request.query?.sort_order ?? null; + const status = request.query?.status ?? null; + const timelineType = request.query?.timeline_type ?? null; const sort = sortField && sortOrder ? { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts index 65f3510c75f8c..83a25ee38cd63 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts @@ -5,11 +5,7 @@ * 2.0. */ -import type * as rt from 'io-ts'; import type { Transform } from 'stream'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; import { createConcatStream, createSplitStream, createMapStream } from '@kbn/utils'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; import { @@ -19,23 +15,15 @@ import { } from '../../../../../utils/read_stream/create_stream_from_ndjson'; import type { ImportTimelineResponse } from './types'; -import { ImportTimelinesSchemaRt } from '../../../../../../common/api/timeline'; -import { throwErrors } from '../../../utils/common'; +import { ImportTimelines } from '../../../../../../common/api/timeline'; +import { parseOrThrowErrorFactory } from '../../../../../../common/timelines/zod_errors'; -type ErrorFactory = (message: string) => Error; +const createPlainError = (message: string) => new Error(message); +const parseOrThrow = parseOrThrowErrorFactory(createPlainError); -export const createPlainError = (message: string) => new Error(message); - -export const decodeOrThrow = - (runtimeType: rt.Type, createError: ErrorFactory = createPlainError) => - (inputValue: I) => - pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); - -export const validateTimelines = (): Transform => +const validateTimelines = (): Transform => createMapStream((obj: ImportTimelineResponse) => - obj instanceof Error - ? new BadRequestError(obj.message) - : decodeOrThrow(ImportTimelinesSchemaRt)(obj) + obj instanceof Error ? new BadRequestError(obj.message) : parseOrThrow(ImportTimelines)(obj) ); export const createTimelinesStreamFromNdJson = (ruleLimit: number) => { return [ diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts index 6f7767247396d..5539631c66a67 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts @@ -10,12 +10,8 @@ import type { Readable } from 'stream'; import { v4 as uuidv4 } from 'uuid'; import { createPromiseFromStreams } from '@kbn/utils'; -import { validate } from '@kbn/securitysolution-io-ts-utils'; -import type { ImportTimelineResultSchema } from '../../../../../../common/api/timeline'; -import { - importTimelineResultSchema, - TimelineStatusEnum, -} from '../../../../../../common/api/timeline'; +import { stringifyZodError } from '@kbn/zod-helpers'; +import { ImportTimelineResult, TimelineStatusEnum } from '../../../../../../common/api/timeline'; import type { BulkError } from '../../../../detection_engine/routes/utils'; import { createBulkErrorObject } from '../../../../detection_engine/routes/utils'; @@ -88,7 +84,7 @@ export const importTimelines = async ( maxTimelineImportExportSize: number, frameworkRequest: FrameworkRequest, isImmutable?: boolean -): Promise => { +): Promise => { const readStream = createTimelinesStreamFromNdJson(maxTimelineImportExportSize); const parsedObjects = await createPromiseFromStreams([file, ...readStream]); @@ -262,17 +258,17 @@ export const importTimelines = async ( const timelinesUpdated = importTimelineResponse.filter( (resp) => isImportRegular(resp) && resp.action === 'updateViaImport' ); - const importTimelinesRes: ImportTimelineResultSchema = { + const importTimelinesRes: ImportTimelineResult = { success: errorsResp.length === 0, success_count: successes.length, errors: errorsResp, timelines_installed: timelinesInstalled.length ?? 0, timelines_updated: timelinesUpdated.length ?? 0, }; - const [validated, errors] = validate(importTimelinesRes, importTimelineResultSchema); - if (errors != null || validated == null) { - return new Error(errors || 'Import timeline error'); + const parseResult = ImportTimelineResult.safeParse(importTimelinesRes); + if (parseResult.success && parseResult.data) { + return parseResult.data; } else { - return validated; + return new Error(stringifyZodError(parseResult.error) || 'Import timeline error'); } }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts index 3415df46e9ac4..5528d92d34240 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts @@ -39,7 +39,6 @@ import { describe('import timelines', () => { let server: ReturnType; - let request: ReturnType; let securitySetup: SecurityPluginSetup; let { context } = requestContextMock.createTools(); let mockGetTimeline: jest.Mock; @@ -452,48 +451,6 @@ describe('import timelines', () => { }); }); }); - - describe('request validation', () => { - beforeEach(() => { - jest.doMock('../../../saved_object/timelines', () => { - return { - getTimelineOrNull: mockGetTimeline.mockReturnValue(null), - persistTimeline: mockPersistTimeline.mockReturnValue({ - timeline: { savedObjectId: '79deb4c0-6bc1-11ea-9999-f5341fb7a189' }, - }), - }; - }); - - jest.doMock('../../../saved_object/pinned_events', () => { - return { - savePinnedEvents: mockPersistPinnedEventOnTimeline.mockReturnValue( - new Error('Test error') - ), - }; - }); - - jest.doMock('../../../saved_object/notes/saved_object', () => { - return { - persistNote: mockPersistNote, - }; - }); - }); - test('disallows invalid query', async () => { - request = requestMock.create({ - method: 'post', - path: TIMELINE_EXPORT_URL, - body: { id: 'someId' }, - }); - const importTimelinesRoute = jest.requireActual('.').importTimelinesRoute; - - importTimelinesRoute(server.router, createMockConfig(), securitySetup); - const result = server.validate(request); - - expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value {"id":"someId"}, excess properties: ["id"]' - ); - }); - }); }); describe('import timeline templates', () => { @@ -904,7 +861,7 @@ describe('import timeline templates', () => { request = requestMock.create({ method: 'post', path: TIMELINE_EXPORT_URL, - body: { id: 'someId' }, + body: { isImmutable: 1 }, }); const importTimelinesRoute = jest.requireActual('.').importTimelinesRoute; @@ -912,7 +869,7 @@ describe('import timeline templates', () => { const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value {"id":"someId"}, excess properties: ["id"]' + "isImmutable: Expected 'true' | 'false', received number" ); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts index be5ebc44e7faa..59a86238941ab 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts @@ -7,18 +7,23 @@ import { extname } from 'path'; import type { Readable } from 'stream'; +import { get } from 'lodash/fp'; +import type { IKibanaResponse } from '@kbn/core-http-server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_IMPORT_URL } from '../../../../../../common/constants'; import type { ConfigType } from '../../../../../config'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { importTimelines } from './helpers'; -import { ImportTimelinesPayloadSchemaRt } from '../../../../../../common/api/timeline'; +import { + ImportTimelinesRequestBody, + type ImportTimelinesResponse, +} from '../../../../../../common/api/timeline'; import { buildFrameworkRequest } from '../../../utils/common'; export { importTimelines } from './helpers'; @@ -39,11 +44,13 @@ export const importTimelinesRoute = (router: SecuritySolutionPluginRouter, confi .addVersion( { validate: { - request: { body: buildRouteValidationWithExcess(ImportTimelinesPayloadSchemaRt) }, + request: { + body: buildRouteValidationWithZod(ImportTimelinesRequestBody), + }, }, version: '2023-10-31', }, - async (context, request, response) => { + async (context, request, response): Promise> => { try { const siemResponse = buildSiemResponse(response); const savedObjectsClient = (await context.core).savedObjects.client; @@ -52,7 +59,7 @@ export const importTimelinesRoute = (router: SecuritySolutionPluginRouter, confi } const { file, isImmutable } = request.body; - const { filename } = file.hapi; + const filename = extractFilename(file); const fileExtension = extname(filename).toLowerCase(); if (fileExtension !== '.ndjson') { @@ -69,8 +76,11 @@ export const importTimelinesRoute = (router: SecuritySolutionPluginRouter, confi frameworkRequest, isImmutable === 'true' ); - if (typeof res !== 'string') return response.ok({ body: res ?? {} }); - else throw res; + if (res instanceof Error || typeof res === 'string') { + throw res; + } else { + return response.ok({ body: res ?? {} }); + } } catch (err) { const error = transformError(err); const siemResponse = buildSiemResponse(response); @@ -82,3 +92,11 @@ export const importTimelinesRoute = (router: SecuritySolutionPluginRouter, confi } ); }; + +function extractFilename(fileObj: unknown) { + const filename = get('hapi.filename', fileObj); + if (filename && typeof filename === 'string') { + return filename; + } + throw new Error('`filename` missing in file'); +} diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts index 1c3cba9fdcb91..1297f0cb1a829 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts @@ -7,22 +7,22 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import type { IKibanaResponse } from '@kbn/core/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_URL } from '../../../../../../common/constants'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; -import type { ConfigType } from '../../../../..'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; -import { patchTimelineSchema } from '../../../../../../common/api/timeline'; +import { + PatchTimelineRequestBody, + type PatchTimelineResponse, +} from '../../../../../../common/api/timeline'; import { buildFrameworkRequest, TimelineStatusActions } from '../../../utils/common'; import { createTimelines } from '../create_timelines'; import { CompareTimelinesStatus } from '../../../utils/compare_timelines_status'; -import type { PatchTimelinesResponse } from '../../../../../../common/api/timeline'; -export const patchTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const patchTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .patch({ path: TIMELINE_URL, @@ -34,11 +34,11 @@ export const patchTimelinesRoute = (router: SecuritySolutionPluginRouter, _: Con .addVersion( { validate: { - request: { body: buildRouteValidationWithExcess(patchTimelineSchema) }, + request: { body: buildRouteValidationWithZod(PatchTimelineRequestBody) }, }, version: '2023-10-31', }, - async (context, request, response): Promise> => { + async (context, request, response): Promise> => { const siemResponse = buildSiemResponse(response); try { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts index b8416b9ffe7bc..cf66c02cf9c97 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts @@ -5,14 +5,13 @@ * 2.0. */ +import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_FAVORITE_URL } from '../../../../../../common/constants'; -import type { ConfigType } from '../../../../..'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; @@ -23,7 +22,7 @@ import { TimelineTypeEnum, } from '../../../../../../common/api/timeline'; -export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .patch({ path: TIMELINE_FAVORITE_URL, @@ -39,7 +38,11 @@ export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter, _: Co request: { body: buildRouteValidationWithZod(PersistFavoriteRouteRequestBody) }, }, }, - async (context, request, response) => { + async ( + context, + request, + response + ): Promise> => { const siemResponse = buildSiemResponse(response); try { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts index 09549f9b9034f..0afc7d21ae296 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts @@ -6,24 +6,24 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import type { IKibanaResponse } from '@kbn/core-http-server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; + import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_RESOLVE_URL } from '../../../../../../common/constants'; -import type { ConfigType } from '../../../../..'; -import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; - import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; -import { getTimelineQuerySchema } from '../../../../../../common/api/timeline'; -import { getTimelineTemplateOrNull, resolveTimelineOrNull } from '../../../saved_object/timelines'; -import type { - SavedTimeline, - ResolvedTimelineWithOutcomeSavedObject, +import { + ResolveTimelineRequestQuery, + type ResolveTimelineResponse, } from '../../../../../../common/api/timeline'; +import { getTimelineTemplateOrNull, resolveTimelineOrNull } from '../../../saved_object/timelines'; +import type { SavedTimeline, ResolvedTimeline } from '../../../../../../common/api/timeline'; -export const resolveTimelineRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { +export const resolveTimelineRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: TIMELINE_RESOLVE_URL, @@ -36,16 +36,16 @@ export const resolveTimelineRoute = (router: SecuritySolutionPluginRouter, _: Co { version: '2023-10-31', validate: { - request: { query: buildRouteValidationWithExcess(getTimelineQuerySchema) }, + request: { query: buildRouteValidationWithZod(ResolveTimelineRequestQuery) }, }, }, - async (context, request, response) => { + async (context, request, response): Promise> => { try { const frameworkRequest = await buildFrameworkRequest(context, request); const query = request.query ?? {}; const { template_timeline_id: templateTimelineId, id } = query; - let res: SavedTimeline | ResolvedTimelineWithOutcomeSavedObject | null = null; + let res: SavedTimeline | ResolvedTimeline | null = null; if (templateTimelineId != null && id == null) { // Template timelineId is not a SO id, so it does not need to be updated to use resolve diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts index a12aaeb010234..571e0f4da8616 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts @@ -13,7 +13,7 @@ import type { Note } from '../../../../../common/api/timeline'; export const persistNotes = async ( frameworkRequest: FrameworkRequest, timelineSavedObjectId: string, - existingNoteIds?: string[], + existingNoteIds?: string[] | null, newNotes?: Note[], overrideOwner: boolean = true ) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts index 2cde936f1c462..88054f0eb6953 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts @@ -16,7 +16,7 @@ import { SavedObjectTimelineType, SavedObjectTimelineStatus, } from '../../../../../common/types/timeline/saved_object'; -import type { TimelineSavedObject } from '../../../../../common/api/timeline'; +import type { TimelineResponse } from '../../../../../common/api/timeline'; import { type TimelineType, TimelineTypeEnum, @@ -49,7 +49,7 @@ const getTimelineTypeAndStatus = ( }; }; -export const convertSavedObjectToSavedTimeline = (savedObject: unknown): TimelineSavedObject => +export const convertSavedObjectToSavedTimeline = (savedObject: unknown): TimelineResponse => pipe( TimelineSavedObjectWithDraftRuntime.decode(savedObject), map((savedTimeline) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts index 20049a63bddba..7e25a38363e2a 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts @@ -23,8 +23,8 @@ import { getNotesByTimelineId, persistNote } from '../notes/saved_object'; import { getAllPinnedEventsByTimelineId, persistPinnedEventOnTimeline } from '../pinned_events'; import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import type { - AllTimelinesResponse, - ResolvedTimelineWithOutcomeSavedObject, + GetTimelinesResponse, + ResolvedTimeline, SavedTimeline, } from '../../../../../common/api/timeline'; import { @@ -141,7 +141,7 @@ describe('saved_object', () => { pageSize: 10, pageIndex: 1, }; - let result = null as unknown as AllTimelinesResponse; + let result = null as unknown as GetTimelinesResponse; beforeEach(async () => { (convertSavedObjectToSavedTimeline as jest.Mock).mockReturnValue(mockGetTimelineValue); mockFindSavedObject = jest @@ -275,7 +275,7 @@ describe('saved_object', () => { describe('resolveTimelineOrNull', () => { let mockResolveSavedObject: jest.Mock; let mockRequest: FrameworkRequest; - let result: ResolvedTimelineWithOutcomeSavedObject | null = null; + let result: ResolvedTimeline | null = null; beforeEach(async () => { (convertSavedObjectToSavedTimeline as jest.Mock).mockReturnValue(mockResolvedTimeline); mockResolveSavedObject = jest.fn().mockReturnValue(mockResolvedSavedObject); 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 08eeda3d8ab56..c3016164d4e7e 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 @@ -19,20 +19,17 @@ import type { Note, BareNote, PinnedEvent, - AllTimelinesResponse, + GetTimelinesResponse, ExportTimelineNotFoundError, PageInfoTimeline, - ResponseTimelines, FavoriteTimelineResponse, - ResponseTimeline, SortTimeline, - TimelineResult, + TimelineResponse, TimelineType, TimelineStatus, - ResolvedTimelineWithOutcomeSavedObject, - TimelineSavedObject, + ResolvedTimeline, SavedTimeline, - TimelineWithoutExternalRefs, + SavedTimelineWithSavedObjectId, } from '../../../../../common/api/timeline'; import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; import type { SavedObjectTimelineWithoutExternalRefs } from '../../../../../common/types/timeline/saved_object'; @@ -49,11 +46,13 @@ import { timelineFieldsMigrator } from './field_migrator'; export { pickSavedTimeline } from './pick_saved_timeline'; export { convertSavedObjectToSavedTimeline } from './convert_saved_object_to_savedtimeline'; +type TimelineWithoutExternalRefs = Omit; + export const getTimeline = async ( request: FrameworkRequest, timelineId: string, timelineType: TimelineType | null = TimelineTypeEnum.default -): Promise => { +): Promise => { let timelineIdToUse = timelineId; try { if (timelineType === TimelineTypeEnum.template) { @@ -77,7 +76,7 @@ export const getTimeline = async ( export const getTimelineOrNull = async ( frameworkRequest: FrameworkRequest, savedObjectId: string -): Promise => { +): Promise => { let timeline = null; try { timeline = await getTimeline(frameworkRequest, savedObjectId); @@ -89,23 +88,19 @@ export const getTimelineOrNull = async ( export const resolveTimelineOrNull = async ( frameworkRequest: FrameworkRequest, savedObjectId: string -): Promise => { - let resolvedTimeline = null; +): Promise => { try { - resolvedTimeline = await resolveSavedTimeline(frameworkRequest, savedObjectId); - // eslint-disable-next-line no-empty - } catch (e) {} - return resolvedTimeline; - // } + const resolvedTimeline = await resolveSavedTimeline(frameworkRequest, savedObjectId); + return resolvedTimeline; + } catch (e) { + return null; + } }; export const getTimelineByTemplateTimelineId = async ( request: FrameworkRequest, templateTimelineId: string -): Promise<{ - totalCount: number; - timeline: TimelineSavedObject[]; -}> => { +): Promise => { const options: SavedObjectsFindOptions = { type: timelineSavedObjectType, filter: `siem-ui-timeline.attributes.templateTimelineId: "${templateTimelineId}"`, @@ -117,7 +112,7 @@ export const getTimelineByTemplateTimelineId = async ( export const getTimelineTemplateOrNull = async ( frameworkRequest: FrameworkRequest, templateTimelineId: string -): Promise => { +): Promise => { let templateTimeline = null; try { templateTimeline = await getTimelineByTemplateTimelineId(frameworkRequest, templateTimelineId); @@ -190,10 +185,7 @@ export const getExistingPrepackagedTimelines = async ( request: FrameworkRequest, countsOnly?: boolean, pageInfo?: PageInfoTimeline -): Promise<{ - totalCount: number; - timeline: TimelineSavedObject[]; -}> => { +): Promise => { const queryPageInfo = countsOnly && pageInfo == null ? { @@ -218,7 +210,7 @@ export const getAllTimeline = async ( sort: SortTimeline | null, status: TimelineStatus | null, timelineType: TimelineType | null -): Promise => { +): Promise => { const searchTerm = search != null ? search : undefined; const searchFields = ['title', 'description']; const filter = combineFilters([ @@ -291,7 +283,7 @@ export const getAllTimeline = async ( export const getDraftTimeline = async ( request: FrameworkRequest, timelineType: TimelineType | null -): Promise => { +): Promise => { const filter = combineFilters([ getTimelineTypeFilter(timelineType ?? null, TimelineStatusEnum.draft), getTimelinesCreatedAndUpdatedByCurrentUser({ request }), @@ -385,13 +377,19 @@ export const persistFavorite = async ( } }; +export interface InternalTimelineResponse { + code: number; + message: string; + timeline: TimelineResponse; +} + export const persistTimeline = async ( request: FrameworkRequest, timelineId: string | null, version: string | null, timeline: SavedTimeline, isImmutable?: boolean -): Promise => { +): Promise => { const savedObjectsClient = (await request.context.core).savedObjects.client; const userInfo = isImmutable ? ({ username: 'Elastic' } as AuthenticatedUser) : request.user; try { @@ -414,7 +412,7 @@ export const persistTimeline = async ( timeline: await getSavedTimeline(request, timelineId), }; } else if (getOr(null, 'output.statusCode', err) === 403) { - const timelineToReturn: TimelineResult = { + const timelineToReturn: TimelineResponse = { ...timeline, savedObjectId: '', version: '', @@ -439,7 +437,7 @@ export const createTimeline = async ({ timeline: SavedTimeline; savedObjectsClient: SavedObjectsClientContract; userInfo: AuthenticatedUser | null; -}) => { +}): Promise => { const { transformedFields: migratedAttributes, references } = timelineFieldsMigrator.extractFieldsToReferences({ data: pickSavedTimeline(timelineId, timeline, userInfo), @@ -479,7 +477,7 @@ const updateTimeline = async ({ savedObjectsClient: SavedObjectsClientContract; userInfo: AuthenticatedUser | null; version: string | null; -}) => { +}): Promise => { const rawTimelineSavedObject = await savedObjectsClient.get( timelineSavedObjectType, @@ -516,11 +514,12 @@ export const updatePartialSavedTimeline = async ( timelineId ); - const { transformedFields, references } = - timelineFieldsMigrator.extractFieldsToReferences({ - data: timeline, - existingReferences: currentSavedTimeline.references, - }); + const { transformedFields, references } = timelineFieldsMigrator.extractFieldsToReferences< + Omit + >({ + data: timeline, + existingReferences: currentSavedTimeline.references, + }); const timelineUpdateAttributes = pickSavedTimeline( null, @@ -588,7 +587,7 @@ export const copyTimeline = async ( request: FrameworkRequest, timeline: SavedTimeline, timelineId: string -): Promise => { +): Promise => { const savedObjectsClient = (await request.context.core).savedObjects.client; // Fetch all objects that need to be copied @@ -658,7 +657,10 @@ const resolveBasicSavedTimeline = async (request: FrameworkRequest, timelineId: }; }; -const resolveSavedTimeline = async (request: FrameworkRequest, timelineId: string) => { +const resolveSavedTimeline = async ( + request: FrameworkRequest, + timelineId: string +): Promise => { const userName = request.user?.username ?? UNAUTHENTICATED_USER; const { resolvedTimelineSavedObject, ...resolveAttributes } = await resolveBasicSavedTimeline( @@ -673,7 +675,6 @@ const resolveSavedTimeline = async (request: FrameworkRequest, timelineId: strin ]); const [notes, pinnedEvents, timeline] = timelineWithNotesAndPinnedEvents; - return { timeline: timelineWithReduxProperties(notes, pinnedEvents, timeline, userName), ...resolveAttributes, @@ -742,9 +743,9 @@ export const convertStringToBase64 = (text: string): string => Buffer.from(text) export const timelineWithReduxProperties = ( notes: Note[], pinnedEvents: PinnedEvent[], - timeline: TimelineSavedObject, + timeline: TimelineResponse, userName: string -): TimelineSavedObject => ({ +): TimelineResponse => ({ ...timeline, favorite: timeline.favorite != null && userName != null @@ -789,7 +790,7 @@ export const getSelectedTimelines = async ( ); const timelineObjects: { - timelines: TimelineSavedObject[]; + timelines: TimelineResponse[]; errors: ExportTimelineNotFoundError[]; } = savedObjects.saved_objects.reduce( (acc, savedObject) => { @@ -805,7 +806,7 @@ export const getSelectedTimelines = async ( return { errors: [...acc.errors, savedObject.error], timelines: acc.timelines }; }, { - timelines: [] as TimelineSavedObject[], + timelines: [] as TimelineResponse[], errors: [] as ExportTimelineNotFoundError[], } ); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts index 50b57bf96bd5b..221e088bdab02 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts @@ -9,14 +9,14 @@ import { isEmpty } from 'lodash/fp'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import { getUserDisplayName } from '@kbn/user-profile-components'; import { UNAUTHENTICATED_USER } from '../../../../../common/constants'; -import type { SavedTimelineWithSavedObjectId } from '../../../../../common/api/timeline'; +import type { SavedTimeline } from '../../../../../common/api/timeline'; import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../../common/api/timeline'; export const pickSavedTimeline = ( timelineId: string | null, - savedTimeline: SavedTimelineWithSavedObjectId, + savedTimeline: SavedTimeline, userInfo: AuthenticatedUser | null -): SavedTimelineWithSavedObjectId => { +): SavedTimeline => { const dateNow = new Date().valueOf(); if (timelineId == null) { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts index a5e88a3ea04d5..caf4bee68e70f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts @@ -7,9 +7,9 @@ import path, { join, resolve } from 'path'; import type { - CheckTimelineStatusRt, - TimelineSavedObject, - ImportTimelinesSchema, + TimelineResponse, + ImportTimelines, + InstallPrepackedTimelinesRequestBody, } from '../../../../common/api/timeline'; import type { FrameworkRequest } from '../../framework'; @@ -19,9 +19,9 @@ import { getExistingPrepackagedTimelines } from '../saved_object/timelines'; import { loadData, getReadables } from './common'; export const getTimelinesToUpdate = ( - timelinesFromFileSystem: ImportTimelinesSchema[], - installedTimelines: TimelineSavedObject[] -): ImportTimelinesSchema[] => { + timelinesFromFileSystem: ImportTimelines[], + installedTimelines: TimelineResponse[] +): ImportTimelines[] => { return timelinesFromFileSystem.filter((timeline) => installedTimelines.some((installedTimeline) => { return ( @@ -34,9 +34,9 @@ export const getTimelinesToUpdate = ( }; export const getTimelinesToInstall = ( - timelinesFromFileSystem: ImportTimelinesSchema[], - installedTimelines: TimelineSavedObject[] -): ImportTimelinesSchema[] => { + timelinesFromFileSystem: ImportTimelines[], + installedTimelines: TimelineResponse[] +): ImportTimelines[] => { return timelinesFromFileSystem.filter( (timeline) => !installedTimelines.some( @@ -49,11 +49,11 @@ export const checkTimelinesStatus = async ( frameworkRequest: FrameworkRequest, filePath?: string, fileName?: string -): Promise => { +): Promise => { let readStream; let timeline: { totalCount: number; - timeline: TimelineSavedObject[]; + timeline: TimelineResponse[]; }; const dir = resolve( join( @@ -75,7 +75,7 @@ export const checkTimelinesStatus = async ( }; } - return loadData<'utf-8', CheckTimelineStatusRt>( + return loadData<'utf-8', InstallPrepackedTimelinesRequestBody>( readStream, (timelinesFromFileSystem: T) => { if (Array.isArray(timelinesFromFileSystem)) { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts index edee5ee4cd912..259719a18bdf0 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type * as rt from 'io-ts'; + import { set } from '@kbn/safer-lodash-set/fp'; import readline from 'readline'; import fs from 'fs'; @@ -13,7 +13,6 @@ import { createListStream } from '@kbn/utils'; import { schema } from '@kbn/config-schema'; import type { KibanaRequest, RequestHandlerContext } from '@kbn/core/server'; -import { formatErrors } from '@kbn/securitysolution-io-ts-utils'; import type { FrameworkRequest } from '../../framework'; @@ -38,12 +37,6 @@ export const buildFrameworkRequest = async ( export const escapeHatch = schema.object({}, { unknowns: 'allow' }); -type ErrorFactory = (message: string) => Error; - -export const throwErrors = (createError: ErrorFactory) => (errors: rt.Errors) => { - throw createError(formatErrors(errors).join('\n')); -}; - export const getReadables = (dataPath: string): Promise => new Promise((resolved, reject) => { const contents: string[] = []; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts index e5e247c1209fe..716bacaacf5ac 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts @@ -26,7 +26,7 @@ import { NOT_ALLOW_UPDATE_STATUS_ERROR_MESSAGE, TEMPLATE_TIMELINE_VERSION_CONFLICT_MESSAGE, } from './failure_cases'; -import type { TimelineSavedObject } from '../../../../common/api/timeline'; +import type { TimelineResponse } from '../../../../common/api/timeline'; import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import { mockGetTimelineValue, mockGetTemplateTimelineValue } from '../__mocks__/import_timelines'; @@ -69,7 +69,7 @@ describe('failure cases', () => { const version = null; const templateTimelineVersion = null; const templateTimelineId = null; - const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTimeline = mockGetTimelineValue as TimelineResponse; const existTemplateTimeline = null; const result = checkIsCreateFailureCases( isHandlingTemplateTimeline, @@ -94,7 +94,7 @@ describe('failure cases', () => { const templateTimelineVersion = 1; const templateTimelineId = 'template-timeline-id-one'; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsCreateFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -144,7 +144,7 @@ describe('failure cases', () => { const templateTimelineVersion = null; const templateTimelineId = null; const existTimeline = { - ...(mockGetTimelineValue as TimelineSavedObject), + ...(mockGetTimelineValue as TimelineResponse), status: TimelineStatusEnum.immutable, }; const existTemplateTimeline = null; @@ -172,7 +172,7 @@ describe('failure cases', () => { const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; const existTemplateTimeline = { - ...(mockGetTemplateTimelineValue as TimelineSavedObject), + ...(mockGetTemplateTimelineValue as TimelineResponse), status: TimelineStatusEnum.immutable, }; const result = checkIsUpdateFailureCases( @@ -198,7 +198,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -246,10 +246,10 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = { - ...(mockGetTemplateTimelineValue as TimelineSavedObject), + ...(mockGetTemplateTimelineValue as TimelineResponse), savedObjectId: 'someOtherId', }; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -273,7 +273,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -297,7 +297,7 @@ describe('failure cases', () => { const templateTimelineVersion = null; const templateTimelineId = null; const existTimeline = { - ...(mockGetTemplateTimelineValue as TimelineSavedObject), + ...(mockGetTemplateTimelineValue as TimelineResponse), savedObjectId: 'someOtherId', }; const existTemplateTimeline = null; @@ -326,7 +326,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsCreateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.draft, @@ -350,7 +350,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsCreateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -373,7 +373,7 @@ describe('failure cases', () => { const version = mockGetTimelineValue.version; const templateTimelineVersion = null; const templateTimelineId = null; - const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTimeline = mockGetTimelineValue as TimelineResponse; const existTemplateTimeline = null; const result = checkIsCreateViaImportFailureCases( isHandlingTemplateTimeline, @@ -399,7 +399,7 @@ describe('failure cases', () => { const version = mockGetTimelineValue.version; const templateTimelineVersion = null; const templateTimelineId = null; - const existTimeline = mockGetTimelineValue as TimelineSavedObject; + const existTimeline = mockGetTimelineValue as TimelineResponse; const existTemplateTimeline = null; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, @@ -424,7 +424,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -448,7 +448,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.immutable, @@ -496,10 +496,10 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = { - ...(mockGetTemplateTimelineValue as TimelineSavedObject), + ...(mockGetTemplateTimelineValue as TimelineResponse), savedObjectId: 'someOtherId', }; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -523,7 +523,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, @@ -547,7 +547,7 @@ describe('failure cases', () => { const templateTimelineVersion = mockGetTemplateTimelineValue.templateTimelineVersion; const templateTimelineId = mockGetTemplateTimelineValue.templateTimelineId; const existTimeline = null; - const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; + const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineResponse; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, TimelineStatusEnum.active, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts index 045793e2251ba..7dace57181239 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts @@ -6,7 +6,7 @@ */ import { isEmpty } from 'lodash/fp'; -import type { TimelineType, TimelineSavedObject } from '../../../../common/api/timeline'; +import type { TimelineType, TimelineResponse } from '../../../../common/api/timeline'; import { type TimelineStatus, TimelineStatusEnum } from '../../../../common/api/timeline'; export const UPDATE_TIMELINE_ERROR_MESSAGE = @@ -42,8 +42,8 @@ export const DEFAULT_ERROR = `Something has gone wrong. We didn't handle somethi const isUpdatingStatus = ( isHandlingTemplateTimeline: boolean, status: TimelineStatus | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { const obj = isHandlingTemplateTimeline ? existTemplateTimeline : existTimeline; return obj?.status === TimelineStatusEnum.immutable ? UPDATE_STATUS_ERROR_MESSAGE : null; @@ -76,8 +76,8 @@ const commonUpdateTemplateTimelineCheck = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (isHandlingTemplateTimeline) { if ( @@ -129,8 +129,8 @@ const commonUpdateTimelineCheck = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (existTimeline == null) { // timeline !exists @@ -158,8 +158,8 @@ const commonUpdateCases = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (isHandlingTemplateTimeline) { return commonUpdateTemplateTimelineCheck( @@ -193,8 +193,8 @@ const createTemplateTimelineCheck = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (isHandlingTemplateTimeline && existTemplateTimeline != null) { // Throw error to create timeline template in patch @@ -219,8 +219,8 @@ export const checkIsUpdateViaImportFailureCases = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (!isHandlingTemplateTimeline) { if (existTimeline == null) { @@ -281,8 +281,8 @@ export const checkIsUpdateFailureCases = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { const error = isUpdatingStatus( isHandlingTemplateTimeline, @@ -315,8 +315,8 @@ export const checkIsCreateFailureCases = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (!isHandlingTemplateTimeline && existTimeline != null) { return { @@ -346,8 +346,8 @@ export const checkIsCreateViaImportFailureCases = ( version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, - existTimeline: TimelineSavedObject | null, - existTemplateTimeline: TimelineSavedObject | null + existTimeline: TimelineResponse | null, + existTemplateTimeline: TimelineResponse | null ) => { if (status === TimelineStatusEnum.draft) { return { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts index 360125e450764..44afe078f9104 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { TimelineSavedObject } from '../../../../common/api/timeline'; +import type { TimelineResponse } from '../../../../common/api/timeline'; import { type TimelineType, TimelineTypeEnum, @@ -27,7 +27,7 @@ export class TimelineObject { public readonly version: string | number | null; private frameworkRequest: FrameworkRequest; - public data: TimelineSavedObject | null; + public data: TimelineResponse | null; constructor({ id = null, diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index 6f3a2c6aacd6d..b1fba21f50367 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -28,6 +28,7 @@ import { BulkPatchRulesRequestBodyInput } from '@kbn/security-solution-plugin/co import { BulkUpdateRulesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.gen'; import { BulkUpsertAssetCriticalityRecordsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/bulk_upload_asset_criticality.gen'; import { CleanDraftTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen'; +import { CopyTimelineRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/copy_timeline/copy_timeline_route.gen'; import { CreateAlertsMigrationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration.gen'; import { CreateAssetCriticalityRecordRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/create_asset_criticality.gen'; import { CreateRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/create_rule/create_rule_route.gen'; @@ -267,6 +268,18 @@ after 30 days. It also deletes other artifacts specific to the migration impleme .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + /** + * Copies and returns a timeline or timeline template. + + */ + copyTimeline(props: CopyTimelineProps, kibanaSpace: string = 'default') { + return supertest + .get(routeWithNamespace('/api/timeline/_copy', kibanaSpace)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, createAlertsIndex(kibanaSpace: string = 'default') { return supertest .post(routeWithNamespace('/api/detection_engine/index', kibanaSpace)) @@ -1303,6 +1316,9 @@ export interface BulkUpsertAssetCriticalityRecordsProps { export interface CleanDraftTimelinesProps { body: CleanDraftTimelinesRequestBodyInput; } +export interface CopyTimelineProps { + body: CopyTimelineRequestBodyInput; +} export interface CreateAlertsMigrationProps { body: CreateAlertsMigrationRequestBodyInput; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts index 6895d79c5aa8e..3206c14eee04d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { - TimelineResult, + TimelineResponse, TimelineTypeEnum, } from '@kbn/security-solution-plugin/common/api/timeline'; import { FtrProviderContext } from '../../../../../api_integration/ftr_provider_context'; @@ -419,8 +419,8 @@ export default function ({ getService }: FtrProviderContext) { }); } -const omitTypename = (key: string, value: keyof TimelineResult) => +const omitTypename = (key: string, value: keyof TimelineResponse) => key === '__typename' ? undefined : value; -const omitTypenameInTimeline = (timeline: TimelineResult) => +const omitTypenameInTimeline = (timeline: TimelineResponse) => JSON.parse(JSON.stringify(timeline), omitTypename); diff --git a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts index a5e732c86b65f..1dcaae65a3392 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { TimelineResponse } from '@kbn/security-solution-plugin/common/api/timeline'; +import type { PersistTimelineResponse } from '@kbn/security-solution-plugin/common/api/timeline'; export interface Timeline { title: string; @@ -69,7 +69,7 @@ export const getTimelineNonValidQuery = (): CompleteTimeline => ({ }); export const expectedExportedTimelineTemplate = ( - templateResponse: Cypress.Response + templateResponse: Cypress.Response ) => { const timelineTemplateBody = templateResponse.body.data.persistTimeline.timeline; @@ -113,7 +113,9 @@ export const expectedExportedTimelineTemplate = ( }; }; -export const expectedExportedTimeline = (timelineResponse: Cypress.Response) => { +export const expectedExportedTimeline = ( + timelineResponse: Cypress.Response +) => { const timelineBody = timelineResponse.body.data.persistTimeline.timeline; return { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts index 864767d0504f9..055d98a2efcfd 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts @@ -6,8 +6,9 @@ */ import type { - AllTimelinesResponse, - TimelineResponse, + DeleteTimelinesResponse, + GetTimelinesResponse, + PatchTimelineResponse, } from '@kbn/security-solution-plugin/common/api/timeline'; import type { CompleteTimeline } from '../../objects/timeline'; import { getTimeline } from '../../objects/timeline'; @@ -21,7 +22,7 @@ const mockTimeline = getTimeline(); * @returns undefined */ export const createTimeline = (timeline: CompleteTimeline = mockTimeline) => - rootRequest({ + rootRequest({ method: 'POST', url: 'api/timeline', body: { @@ -75,7 +76,7 @@ export const createTimeline = (timeline: CompleteTimeline = mockTimeline) => * @returns undefined */ export const createTimelineTemplate = (timeline: CompleteTimeline = mockTimeline) => - rootRequest({ + rootRequest({ method: 'POST', url: 'api/timeline', body: { @@ -159,7 +160,7 @@ export const favoriteTimeline = ({ }); export const getAllTimelines = () => - rootRequest({ + rootRequest({ method: 'GET', url: 'api/timelines?page_size=100&page_index=1&sort_field=updated&sort_order=desc&timeline_type=default', }); @@ -167,7 +168,7 @@ export const getAllTimelines = () => export const deleteTimelines = () => { getAllTimelines().then(($timelines) => { const savedObjectIds = $timelines.body.timeline.map((timeline) => timeline.savedObjectId); - rootRequest({ + rootRequest({ method: 'DELETE', url: 'api/timeline', body: { diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts index 16fb1bb48808a..580b96bfd2340 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts @@ -6,7 +6,7 @@ */ import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data'; -import { TimelineResponse } from '@kbn/security-solution-plugin/common/api/timeline'; +import { PatchTimelineResponse } from '@kbn/security-solution-plugin/common/api/timeline'; // @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail import { kibanaPackageJson } from '@kbn/repo-info'; import { type IndexedEndpointRuleAlerts } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/index_endpoint_rule_alerts'; @@ -64,7 +64,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // failing tests: https://github.com/elastic/kibana/issues/170705 describe.skip('from Timeline', () => { - let timeline: TimelineResponse; + let timeline: PatchTimelineResponse; before(async () => { log.info( diff --git a/x-pack/test/security_solution_ftr/services/timeline/index.ts b/x-pack/test/security_solution_ftr/services/timeline/index.ts index ebf6497567d81..8195f63ffc246 100644 --- a/x-pack/test/security_solution_ftr/services/timeline/index.ts +++ b/x-pack/test/security_solution_ftr/services/timeline/index.ts @@ -8,8 +8,12 @@ import { Response } from 'superagent'; import { EndpointError } from '@kbn/security-solution-plugin/common/endpoint/errors'; import { TIMELINE_DRAFT_URL, TIMELINE_URL } from '@kbn/security-solution-plugin/common/constants'; -import { TimelineResponse } from '@kbn/security-solution-plugin/common/api/timeline'; -import { TimelineInput } from '@kbn/security-solution-plugin/common/search_strategy'; +import { + DeleteTimelinesResponse, + GetDraftTimelinesResponse, + PatchTimelineResponse, + SavedTimeline, +} from '@kbn/security-solution-plugin/common/api/timeline'; import moment from 'moment'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { FtrService } from '../../../functional/ftr_provider_context'; @@ -52,7 +56,7 @@ export class TimelineTestService extends FtrService { * for display (not sure why). TO get around this, just select a date range from the user date * picker and that seems to trigger the events to be fetched. */ - async createTimeline(title: string): Promise { + async createTimeline(title: string): Promise { // Create a new timeline draft const createdTimeline = ( await this.supertest @@ -61,7 +65,7 @@ export class TimelineTestService extends FtrService { .set('elastic-api-version', '2023-10-31') .send({ timelineType: 'default' }) .then(this.getHttpResponseFailureHandler()) - .then((response) => response.body as TimelineResponse) + .then((response) => response.body as GetDraftTimelinesResponse) ).data.persistTimeline.timeline; this.log.info('Draft timeline:'); @@ -71,7 +75,7 @@ export class TimelineTestService extends FtrService { const { savedObjectId: timelineId, version } = createdTimeline; - const timelineUpdate: TimelineInput = { + const timelineUpdate: SavedTimeline = { title, // Set date range to the last 1 year dateRange: { @@ -79,7 +83,7 @@ export class TimelineTestService extends FtrService { end: moment().toISOString(), // Not sure why `start`/`end` are defined as numbers in the type, but looking at the // UI's use of it, I can see they are being set to strings, so I'm forcing a cast here - } as unknown as TimelineInput['dateRange'], + } as unknown as SavedTimeline['dateRange'], // Not sure why, but the following fields are not in the created timeline, which causes // the timeline to not be able to pull in the event for display @@ -107,9 +111,9 @@ export class TimelineTestService extends FtrService { async updateTimeline( timelineId: string, - updates: TimelineInput, + updates: SavedTimeline, version: string - ): Promise { + ): Promise { return await this.supertest .patch(TIMELINE_URL) .set('kbn-xsrf', 'true') @@ -120,11 +124,11 @@ export class TimelineTestService extends FtrService { timeline: updates, }) .then(this.getHttpResponseFailureHandler()) - .then((response) => response.body as TimelineResponse); + .then((response) => response.body as PatchTimelineResponse); } /** Deletes a timeline using it timeline id */ - async deleteTimeline(id: string | string[]): Promise { + async deleteTimeline(id: string | string[]) { await this.supertest .delete(TIMELINE_URL) .set('kbn-xsrf', 'true') @@ -133,7 +137,7 @@ export class TimelineTestService extends FtrService { savedObjectIds: Array.isArray(id) ? id : [id], }) .then(this.getHttpResponseFailureHandler()) - .then((response) => response.body as TimelineResponse); + .then((response) => response.body as DeleteTimelinesResponse); } /** @@ -173,7 +177,7 @@ export class TimelineTestService extends FtrService { /** If defined, then only alerts from the specific `agent.id` will be displayed */ endpointAgentId: string; }> - ): Promise { + ): Promise { const newTimeline = await this.createTimeline(title); const { expression, esQuery } = this.getEndpointAlertsKqlQuery(endpointAgentId);