[Threat Hunting Investigations] Migrate all timeline routes to OpenAPI types (#190238)

## Summary

fixes: https://github.com/elastic/security-team/issues/10235
fixes: https://github.com/elastic/security-team/issues/10237

This is the final PR for migrating over all timeline-related schemas and
types to the new generated zod schemas from our OpenAPI specs. (see
https://github.com/elastic/security-team/issues/10110)
On top of moving to the new schemas/types, this PR also cleans up usage
of now outdated types.

I'm aware of the size of this PR but rest assured, the changes are easy
to review and for most teams, only a handful of files need to be
reviewed:

```markdown
### elastic/security-defend-workflows

* x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts

### elastic/security-detection-rule-management

* 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
* x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/perform_timelines_installation.ts

### elastic/security-detections-response

* x-pack/test/security_solution_cypress/cypress/objects/timeline.ts

### elastic/security-engineering-productivity

* x-pack/test/security_solution_cypress/cypress/objects/timeline.ts
* x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts
```


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Jan Monschke 2024-09-30 20:55:23 +02:00 committed by GitHub
parent 7fd3317423
commit 00789609ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
107 changed files with 2042 additions and 2689 deletions

1
.github/CODEOWNERS vendored
View file

@ -1532,6 +1532,7 @@ x-pack/test/security_solution_api_integration/test_suites/sources @elastic/secur
x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout @elastic/security-threat-hunting-investigations
x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/security-threat-hunting-investigations
/x-pack/plugins/security_solution/common/timelines @elastic/security-threat-hunting-investigations
/x-pack/plugins/security_solution/public/common/components/alerts_viewer @elastic/security-threat-hunting-investigations
/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_action @elastic/security-threat-hunting-investigations
/x-pack/plugins/security_solution/public/common/components/event_details @elastic/security-threat-hunting-investigations

View file

@ -16356,19 +16356,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 Timeline or Timeline template details
tags:
@ -16405,23 +16407,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
@ -16483,21 +16470,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:
@ -16514,6 +16488,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:
description: >-
@ -16532,23 +16537,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:
@ -16610,23 +16600,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
@ -16782,28 +16757,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:
@ -16811,13 +16772,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:
@ -16876,7 +16832,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:
@ -16899,13 +16857,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:
@ -16943,19 +16896,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
@ -17024,36 +16974,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:
@ -31335,30 +31275,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
@ -31380,6 +31329,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:
@ -31467,41 +31420,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
@ -31566,6 +31537,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'
@ -31586,6 +31563,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'
@ -31623,34 +31617,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:
@ -31685,6 +31674,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:
@ -31713,12 +31713,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
@ -31808,6 +31812,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:
@ -31857,27 +31873,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

View file

@ -20445,19 +20445,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 Timeline or Timeline template details
tags:
@ -20494,23 +20496,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
@ -20572,21 +20559,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:
@ -20603,6 +20577,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:
description: >-
@ -20621,23 +20626,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:
@ -20699,23 +20689,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
@ -20871,28 +20846,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:
@ -20900,13 +20861,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:
@ -20965,7 +20921,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:
@ -20988,13 +20946,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:
@ -21032,19 +20985,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
@ -21113,36 +21063,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:
@ -39344,30 +39284,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
@ -39389,6 +39338,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:
@ -39476,41 +39429,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
@ -39575,6 +39546,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'
@ -39595,6 +39572,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'
@ -39632,34 +39626,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:
@ -39694,6 +39683,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:
@ -39722,12 +39722,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
@ -39817,6 +39821,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:
@ -39866,27 +39882,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

View file

@ -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,
@ -556,6 +560,23 @@ If asset criticality records already exist for the specified entities, those rec
})
.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<CopyTimelineResponse>({
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
@ -1993,6 +2014,9 @@ export interface BulkUpsertAssetCriticalityRecordsProps {
export interface CleanDraftTimelinesProps {
body: CleanDraftTimelinesRequestBodyInput;
}
export interface CopyTimelineProps {
body: CopyTimelineRequestBodyInput;
}
export interface CreateAlertsMigrationProps {
body: CreateAlertsMigrationRequestBodyInput;
}

View file

@ -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<typeof CleanDraftTimelinesRequestBody>;
export const CleanDraftTimelinesRequestBody = z.object({
@ -25,10 +25,4 @@ export const CleanDraftTimelinesRequestBody = z.object({
export type CleanDraftTimelinesRequestBodyInput = z.input<typeof CleanDraftTimelinesRequestBody>;
export type CleanDraftTimelinesResponse = z.infer<typeof CleanDraftTimelinesResponse>;
export const CleanDraftTimelinesResponse = z.object({
data: z.object({
persistTimeline: z.object({
timeline: TimelineResponse,
}),
}),
});
export const CleanDraftTimelinesResponse = PersistTimelineResponse;

View file

@ -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:
@ -39,19 +32,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:

View file

@ -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<typeof CopyTimelineRequestBody>;
export const CopyTimelineRequestBody = z.object({
timeline: SavedTimeline,
timelineIdToCopy: z.string(),
});
export type CopyTimelineRequestBodyInput = z.input<typeof CopyTimelineRequestBody>;
export type CopyTimelineResponse = z.infer<typeof CopyTimelineResponse>;
export const CopyTimelineResponse = PersistTimelineResponse;

View file

@ -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'

View file

@ -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,
});

View file

@ -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<typeof CreateTimelinesRequestBody>;
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<typeof CreateTimelinesRequestBody>;
export type CreateTimelinesResponse = z.infer<typeof CreateTimelinesResponse>;
export const CreateTimelinesResponse = z.object({
data: z.object({
persistTimeline: z.object({
timeline: TimelineResponse.optional(),
}),
}),
});
export const CreateTimelinesResponse = PersistTimelineResponse;

View file

@ -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:
@ -29,9 +22,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
@ -50,26 +44,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:

View file

@ -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;
};
}

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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<typeof GetDraftTimelinesRequestQuery>;
export const GetDraftTimelinesRequestQuery = z.object({
@ -25,10 +25,4 @@ export const GetDraftTimelinesRequestQuery = z.object({
export type GetDraftTimelinesRequestQueryInput = z.input<typeof GetDraftTimelinesRequestQuery>;
export type GetDraftTimelinesResponse = z.infer<typeof GetDraftTimelinesResponse>;
export const GetDraftTimelinesResponse = z.object({
data: z.object({
persistTimeline: z.object({
timeline: TimelineResponse,
}),
}),
});
export const GetDraftTimelinesResponse = PersistTimelineResponse;

View file

@ -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:
@ -31,19 +24,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:

View file

@ -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,
});

View file

@ -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:

View file

@ -32,8 +32,11 @@ export const GetTimelineRequestQuery = z.object({
export type GetTimelineRequestQueryInput = z.input<typeof GetTimelineRequestQuery>;
export type GetTimelineResponse = z.infer<typeof GetTimelineResponse>;
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(),
]);

View file

@ -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:
@ -39,13 +32,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

View file

@ -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<typeof getTimelineQuerySchema>;

View file

@ -41,13 +41,11 @@ export type GetTimelinesRequestQueryInput = z.input<typeof GetTimelinesRequestQu
export type GetTimelinesResponse = z.infer<typeof GetTimelinesResponse>;
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(),
});

View file

@ -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:
@ -75,37 +68,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:

View file

@ -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),
});

View file

@ -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<typeof ImportTimelinesRequestBody>;
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<typeof ImportTimelinesRequestBody>;
export type ImportTimelinesResponse = z.infer<typeof ImportTimelinesResponse>;
export const ImportTimelinesResponse = z.object({
data: ImportTimelineResult,
});
export const ImportTimelinesResponse = ImportTimelineResult;

View file

@ -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:
@ -29,37 +22,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.

View file

@ -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<typeof ImportTimelinesSchemaRt>;
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 }),
]);

View file

@ -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';

View file

@ -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<typeof checkTimelineStatusRt>;

View file

@ -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<typeof InstallPrepackedTimelinesResponse>;
export const InstallPrepackedTimelinesResponse = z.object({
data: ImportTimelineResult,
});
export const InstallPrepackedTimelinesResponse = ImportTimelineResult;

View file

@ -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:
@ -41,18 +34,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:

View file

@ -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,412 +43,54 @@ 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<BarePinnedEvent, 'timelineId'>;
/**
* Outcome is a property of the saved object resolve api
* will tell us info about the rule after 8.0 migrations
*/
export type SavedObjectResolveOutcome = runtimeTypes.TypeOf<typeof SavedObjectResolveOutcome>;
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 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),
}),
]);
/**
* This type represents a note type stored in a saved object that does not include any fields that reference
* other saved objects.
*/
export type BareNoteWithoutExternalRefs = Omit<BareNote, 'timelineId'>;
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<typeof SavedSortRuntimeType>;
/*
* 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<typeof SavedTimelineRuntimeType>;
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<SavedTimeline, 'dataViewId' | 'savedQueryId'>;
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<typeof SingleTimelineResponseType>;
/** 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<typeof responseTimelines>;
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<typeof allTimelinesResponse>;
export type BarePinnedEventWithoutExternalRefs = Omit<BarePinnedEvent, 'timelineId'>;
/**
* All Timeline Saved object type with metadata
* This type represents a note type stored in a saved object that does not include any fields that reference
* other saved objects.
*/
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 type BareNoteWithoutExternalRefs = Omit<BareNote, 'timelineId'>;
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<typeof TimelineErrorResponseType>;
export type TimelineResponse = runtimeTypes.TypeOf<typeof TimelineResponseType>;
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,
});
export const RowRendererCount = Object.keys(RowRendererIdEnum).length;
export const RowRendererValues = Object.values(RowRendererId.Values);
/**
* 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<typeof importTimelineResultSchema>;
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<typeof getTimelinesArgs>;
export interface ColumnHeaderResult {
aggregatable?: Maybe<boolean>;
category?: Maybe<string>;
columnHeaderType?: Maybe<string>;
description?: Maybe<string>;
example?: Maybe<string | number>;
indexes?: Maybe<string[]>;
id?: Maybe<string>;
name?: Maybe<string>;
placeholder?: Maybe<string>;
searchable?: Maybe<boolean>;
type?: Maybe<string>;
}
export interface DataProviderResult {
id?: Maybe<string>;
name?: Maybe<string>;
enabled?: Maybe<boolean>;
excluded?: Maybe<boolean>;
kqlQuery?: Maybe<string>;
queryMatch?: Maybe<QueryMatchResult>;
type?: Maybe<DataProviderType>;
and?: Maybe<DataProviderResult[]>;
}
export interface QueryMatchResult {
field?: Maybe<string>;
displayField?: Maybe<string>;
value?: Maybe<string | string[]>;
displayValue?: Maybe<string>;
operator?: Maybe<string>;
}
export interface DateRangePickerResult {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
start?: Maybe<any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
end?: Maybe<any>;
}
export interface EqlOptionsResult {
eventCategoryField?: Maybe<string>;
tiebreakerField?: Maybe<string>;
timestampField?: Maybe<string>;
query?: Maybe<string>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
size?: Maybe<any>;
}
export interface FilterTimelineResult {
exists?: Maybe<string>;
meta?: Maybe<FilterMetaTimelineResult>;
match_all?: Maybe<string>;
missing?: Maybe<string>;
query?: Maybe<string>;
range?: Maybe<string>;
script?: Maybe<string>;
}
export interface FilterMetaTimelineResult {
alias?: Maybe<string>;
controlledBy?: Maybe<string>;
disabled?: Maybe<boolean>;
field?: Maybe<string>;
formattedValue?: Maybe<string>;
index?: Maybe<string>;
key?: Maybe<string>;
negate?: Maybe<boolean>;
params?: Maybe<string>;
type?: Maybe<string>;
value?: Maybe<string>;
}
export interface SerializedFilterQueryResult {
filterQuery?: Maybe<SerializedKueryQueryResult>;
}
export interface KueryFilterQueryResult {
kind?: Maybe<string>;
expression?: Maybe<string>;
}
export interface SerializedKueryQueryResult {
kuery?: Maybe<KueryFilterQueryResult>;
serializedQuery?: Maybe<string>;
}
export interface TimelineResult {
columns?: Maybe<ColumnHeaderResult[]>;
created?: Maybe<number>;
createdBy?: Maybe<string>;
dataProviders?: Maybe<DataProviderResult[]>;
dataViewId?: Maybe<string>;
dateRange?: Maybe<DateRangePickerResult>;
description?: Maybe<string>;
eqlOptions?: Maybe<EqlOptionsResult>;
eventIdToNoteIds?: Maybe<Note[]>;
eventType?: Maybe<string>;
excludedRowRendererIds?: Maybe<RowRendererId[]>;
favorite?: Maybe<FavoriteTimelineResult[]>;
filters?: Maybe<FilterTimelineResult[]>;
kqlMode?: Maybe<string>;
kqlQuery?: Maybe<SerializedFilterQueryResult>;
indexNames?: Maybe<string[]>;
notes?: Maybe<Note[]>;
noteIds?: Maybe<string[]>;
pinnedEventIds?: Maybe<string[]>;
pinnedEventsSaveObject?: Maybe<PinnedEvent[]>;
savedQueryId?: Maybe<string>;
savedObjectId: string;
sort?: Maybe<Sort>;
status?: Maybe<TimelineStatus>;
title?: Maybe<string>;
templateTimelineId?: Maybe<string>;
templateTimelineVersion?: Maybe<number>;
timelineType?: Maybe<TimelineType>;
updated?: Maybe<number>;
updatedBy?: Maybe<string>;
version: string;
savedSearchId?: Maybe<string>;
}
export interface ResponseTimeline {
code?: Maybe<number>;
message?: Maybe<string>;
timeline: TimelineResult;
}
export interface SortTimeline {
sortField: SortFieldTimeline;
sortOrder: Direction;
}
export interface GetAllTimelineVariables {
pageInfo: PageInfoTimeline;
search?: Maybe<string>;
sort?: Maybe<SortTimeline>;
onlyUserFavorite?: Maybe<boolean>;
timelineType?: Maybe<TimelineType>;
status?: Maybe<TimelineStatus>;
sortOrder: SortDirection;
}

View file

@ -42,24 +42,24 @@ export const TemplateTimelineTypeEnum = TemplateTimelineType.enum;
export type ColumnHeaderResult = z.infer<typeof ColumnHeaderResult>;
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<typeof QueryMatchResult>;
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<typeof DataProviderResult>;
@ -119,27 +120,28 @@ export const FavoriteTimelineResult = z.object({
export type FilterTimelineResult = z.infer<typeof FilterTimelineResult>;
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<typeof SerializedFilterQueryResult>;
@ -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<typeof SavedTimelineWithSavedObjectId>;
export const SavedTimelineWithSavedObjectId = SavedTimeline.merge(
z.object({
savedObjectId: z.string(),
version: z.string(),
})
);
export type BareNote = z.infer<typeof BareNote>;
export const BareNote = z.object({
eventId: z.string().nullable().optional(),
@ -251,18 +261,50 @@ export const PinnedEvent = BarePinnedEvent.merge(
);
export type TimelineResponse = z.infer<typeof TimelineResponse>;
export const TimelineResponse = SavedTimeline.merge(
export const TimelineResponse = SavedTimeline.merge(SavedTimelineWithSavedObjectId).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 TimelineSavedToReturnObject = z.infer<typeof TimelineSavedToReturnObject>;
export const TimelineSavedToReturnObject = SavedTimeline.merge(
z.object({
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<typeof SavedObjectResolveOutcome>;
export const SavedObjectResolveOutcome = z.enum(['exactMatch', 'aliasMatch', 'conflict']);
export type SavedObjectResolveOutcomeEnum = typeof SavedObjectResolveOutcome.enum;
export const SavedObjectResolveOutcomeEnum = SavedObjectResolveOutcome.enum;
export type SavedObjectResolveAliasPurpose = z.infer<typeof SavedObjectResolveAliasPurpose>;
export const SavedObjectResolveAliasPurpose = z.enum([
'savedObjectConversion',
'savedObjectImport',
]);
export type SavedObjectResolveAliasPurposeEnum = typeof SavedObjectResolveAliasPurpose.enum;
export const SavedObjectResolveAliasPurposeEnum = SavedObjectResolveAliasPurpose.enum;
export type ResolvedTimeline = z.infer<typeof ResolvedTimeline>;
export const ResolvedTimeline = z.object({
timeline: TimelineSavedToReturnObject,
outcome: SavedObjectResolveOutcome,
alias_target_id: z.string().optional(),
alias_purpose: SavedObjectResolveAliasPurpose.optional(),
});
export type FavoriteTimelineResponse = z.infer<typeof FavoriteTimelineResponse>;
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<typeof PersistTimelineResponse>;
export const PersistTimelineResponse = z.object({
data: z.object({
persistTimeline: z.object({
timeline: TimelineResponse,
}),
}),
});
export type BareNoteWithoutExternalRefs = z.infer<typeof BareNoteWithoutExternalRefs>;
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<typeof SortDirection>;
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<typeof ImportTimelines>;
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<typeof ExportedTimelines>;
export const ExportedTimelines = SavedTimeline.merge(
export type TimelineErrorResponse = z.infer<typeof TimelineErrorResponse>;
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<typeof Readable>;
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(),
}),
]);

View file

@ -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
TimelineResponse:
SavedTimelineWithSavedObjectId:
allOf:
- $ref: '#/components/schemas/SavedTimeline'
- type: object
required:
- savedObjectId
- version
required: [savedObjectId, version]
properties:
eventIdToNoteIds:
type: array
items:
$ref: '#/components/schemas/Note'
notes:
type: array
items:
$ref: '#/components/schemas/Note'
noteIds:
type: array
items:
type: string
pinnedEventIds:
type: array
items:
type: string
pinnedEventsSaveObject:
type: array
items:
$ref: '#/components/schemas/PinnedEvent'
savedObjectId:
type: string
version:
type: string
TimelineResponse:
allOf:
- $ref: '#/components/schemas/SavedTimeline'
- $ref: '#/components/schemas/SavedTimelineWithSavedObjectId'
- type: object
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'
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
type: array
items:
$ref: '#/components/schemas/BareNote'
eventNotes:
nullable: true
type: array
items:
$ref: '#/components/schemas/BareNote'
pinnedEventIds:
nullable: true
type: array
nullable: true
items:
type: string
eventNotes:
type: array
nullable: true
items:
$ref: '#/components/schemas/BareNote'
globalNotes:
type: array
nullable: true
items:
$ref: '#/components/schemas/BareNote'
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

View file

@ -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<typeof PatchTimelineRequestBody>;
export const PatchTimelineRequestBody = z.object({
@ -27,10 +27,4 @@ export const PatchTimelineRequestBody = z.object({
export type PatchTimelineRequestBodyInput = z.input<typeof PatchTimelineRequestBody>;
export type PatchTimelineResponse = z.infer<typeof PatchTimelineResponse>;
export const PatchTimelineResponse = z.object({
data: z.object({
persistTimeline: z.object({
timeline: TimelineResponse,
}),
}),
});
export const PatchTimelineResponse = PersistTimelineResponse;

View file

@ -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:

View file

@ -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;
};
}

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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,
]);

View file

@ -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<typeof ResolveTimelineRequestQuery>;
export const ResolveTimelineRequestQuery = z.object({
@ -32,8 +32,9 @@ export const ResolveTimelineRequestQuery = z.object({
export type ResolveTimelineRequestQueryInput = z.input<typeof ResolveTimelineRequestQuery>;
export type ResolveTimelineResponse = z.infer<typeof ResolveTimelineResponse>;
export const ResolveTimelineResponse = z.object({
data: z.object({
getOneTimeline: TimelineResponse.nullable(),
export const ResolveTimelineResponse = z.union([
z.object({
data: ResolvedTimeline,
}),
});
z.object({}).strict(),
]);

View file

@ -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':

View file

@ -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';

View file

@ -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<Field = string> extends SortField<Fiel
esTypes: string[];
}
export interface ColumnHeaderInput {
aggregatable?: Maybe<boolean>;
category?: Maybe<string>;
columnHeaderType?: Maybe<string>;
description?: Maybe<string>;
example?: Maybe<string>;
indexes?: Maybe<string[]>;
id?: Maybe<string>;
name?: Maybe<string>;
placeholder?: Maybe<string>;
searchable?: Maybe<boolean>;
type?: Maybe<string>;
}
export interface QueryMatchInput {
field?: Maybe<string>;
displayField?: Maybe<string>;
value?: Maybe<string>;
displayValue?: Maybe<string>;
operator?: Maybe<string>;
}
export interface DataProviderInput {
id?: Maybe<string>;
name?: Maybe<string>;
enabled?: Maybe<boolean>;
excluded?: Maybe<boolean>;
kqlQuery?: Maybe<string>;
queryMatch?: Maybe<QueryMatchInput>;
and?: Maybe<DataProviderInput[]>;
type?: Maybe<DataProviderType>;
}
export interface EqlOptionsInput {
eventCategoryField?: Maybe<string>;
tiebreakerField?: Maybe<string>;
timestampField?: Maybe<string>;
query?: Maybe<string>;
size?: Maybe<number>;
}
export interface FilterMetaTimelineInput {
alias?: Maybe<string>;
controlledBy?: Maybe<string>;
disabled?: Maybe<boolean>;
field?: Maybe<string>;
formattedValue?: Maybe<string>;
index?: Maybe<string>;
key?: Maybe<string>;
negate?: Maybe<boolean>;
params?: Maybe<string>;
type?: Maybe<string>;
value?: Maybe<string>;
}
export interface FilterTimelineInput {
exists?: Maybe<string>;
meta?: Maybe<FilterMetaTimelineInput>;
match_all?: Maybe<string>;
missing?: Maybe<string>;
query?: Maybe<string>;
range?: Maybe<string>;
script?: Maybe<string>;
}
export interface SerializedFilterQueryInput {
filterQuery?: Maybe<SerializedKueryQueryInput>;
}
export interface SerializedKueryQueryInput {
kuery?: Maybe<KueryFilterQueryInput>;
serializedQuery?: Maybe<string>;
}
export interface KueryFilterQueryInput {
kind?: Maybe<string>;
expression?: Maybe<string>;
}
export interface DateRangePickerInput {
start?: Maybe<number>;
end?: Maybe<number>;
}
export interface SortTimelineInput {
columnId?: Maybe<string>;
sortDirection?: Maybe<string>;
}
export interface TimelineInput {
columns?: Maybe<ColumnHeaderInput[]>;
dataProviders?: Maybe<DataProviderInput[]>;
dataViewId?: Maybe<string>;
description?: Maybe<string>;
eqlOptions?: Maybe<EqlOptionsInput>;
eventType?: Maybe<string>;
excludedRowRendererIds?: Maybe<RowRendererId[]>;
filters?: Maybe<FilterTimelineInput[]>;
kqlMode?: Maybe<string>;
kqlQuery?: Maybe<SerializedFilterQueryInput>;
indexNames?: Maybe<string[]>;
title?: Maybe<string>;
templateTimelineId?: Maybe<string>;
templateTimelineVersion?: Maybe<number>;
timelineType?: Maybe<TimelineType>;
dateRange?: Maybe<DateRangePickerInput>;
savedQueryId?: Maybe<string>;
sort?: Maybe<SortTimelineInput[]>;
status?: Maybe<TimelineStatus>;
savedSearchId: Maybe<string>;
}
export enum FlowDirection {
uniDirectional = 'uniDirectional',
biDirectional = 'biDirectional',

View file

@ -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 =
<T>(zodType: ZodType<T>) =>
(v: unknown): Either<ZodError<T>, 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));

View file

@ -268,18 +268,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 Timeline or Timeline template details
tags:
@ -316,22 +318,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
@ -393,20 +380,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:
@ -423,6 +397,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:
description: >-
@ -441,22 +445,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:
@ -518,22 +507,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
@ -688,28 +662,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:
@ -717,12 +677,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:
@ -781,7 +736,8 @@ paths:
properties:
prepackagedTimelines:
items:
$ref: '#/components/schemas/SavedTimeline'
$ref: '#/components/schemas/TimelineSavedToReturnObject'
nullable: true
type: array
timelinesToInstall:
items:
@ -804,12 +760,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:
@ -847,18 +798,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
@ -927,35 +875,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:
@ -1025,30 +963,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
@ -1070,6 +1017,10 @@ components:
type: string
queryMatch:
$ref: '#/components/schemas/QueryMatchResult'
nullable: true
type:
$ref: '#/components/schemas/DataProviderType'
nullable: true
DataProviderResult:
type: object
properties:
@ -1157,41 +1108,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
@ -1256,6 +1225,12 @@ components:
version:
nullable: true
type: string
required:
- savedObjectId
- version
- pinnedEventIds
- eventNotes
- globalNotes
Note:
allOf:
- $ref: '#/components/schemas/BareNote'
@ -1275,6 +1250,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'
@ -1312,34 +1304,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:
@ -1374,6 +1359,17 @@ components:
- threat_match
- zeek
type: string
SavedObjectResolveAliasPurpose:
enum:
- savedObjectConversion
- savedObjectImport
type: string
SavedObjectResolveOutcome:
enum:
- exactMatch
- aliasMatch
- conflict
type: string
SavedTimeline:
type: object
properties:
@ -1402,12 +1398,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
@ -1496,6 +1496,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:
@ -1545,27 +1557,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

View file

@ -268,18 +268,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 Timeline or Timeline template details
tags:
@ -316,22 +318,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
@ -393,20 +380,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:
@ -423,6 +397,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:
description: >-
@ -441,22 +445,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:
@ -518,22 +507,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
@ -688,28 +662,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:
@ -717,12 +677,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:
@ -781,7 +736,8 @@ paths:
properties:
prepackagedTimelines:
items:
$ref: '#/components/schemas/SavedTimeline'
$ref: '#/components/schemas/TimelineSavedToReturnObject'
nullable: true
type: array
timelinesToInstall:
items:
@ -804,12 +760,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:
@ -847,18 +798,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
@ -927,35 +875,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:
@ -1025,30 +963,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
@ -1070,6 +1017,10 @@ components:
type: string
queryMatch:
$ref: '#/components/schemas/QueryMatchResult'
nullable: true
type:
$ref: '#/components/schemas/DataProviderType'
nullable: true
DataProviderResult:
type: object
properties:
@ -1157,41 +1108,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
@ -1256,6 +1225,12 @@ components:
version:
nullable: true
type: string
required:
- savedObjectId
- version
- pinnedEventIds
- eventNotes
- globalNotes
Note:
allOf:
- $ref: '#/components/schemas/BareNote'
@ -1275,6 +1250,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'
@ -1312,34 +1304,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:
@ -1374,6 +1359,17 @@ components:
- threat_match
- zeek
type: string
SavedObjectResolveAliasPurpose:
enum:
- savedObjectConversion
- savedObjectImport
type: string
SavedObjectResolveOutcome:
enum:
- exactMatch
- aliasMatch
- conflict
type: string
SavedTimeline:
type: object
properties:
@ -1402,12 +1398,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
@ -1496,6 +1496,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:
@ -1545,27 +1557,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

View file

@ -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',

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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;

View file

@ -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,

View file

@ -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<T = string> extends RequestPostTimeline {
type RequestPersistTimeline = RequestPostTimeline & Partial<RequestPatchTimeline<null | string>>;
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<TimelineResponse | TimelineErrorResponse> => {
}: RequestPostTimeline): Promise<CreateTimelinesResponse | TimelineErrorResponse> => {
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<TimelineResponse>(TIMELINE_URL, {
const response = await KibanaServices.get().http.post<CreateTimelinesResponse>(TIMELINE_URL, {
method: 'POST',
body: requestBody,
version: '2023-10-31',
@ -131,7 +112,7 @@ const patchTimeline = async ({
timeline,
version,
savedSearch,
}: RequestPatchTimeline): Promise<TimelineResponse | TimelineErrorResponse> => {
}: RequestPatchTimeline): Promise<PatchTimelineResponse | TimelineErrorResponse> => {
let response = null;
let requestBody = null;
try {
@ -153,7 +134,7 @@ const patchTimeline = async ({
}
try {
response = await KibanaServices.get().http.patch<TimelineResponse>(TIMELINE_URL, {
response = await KibanaServices.get().http.patch<PatchTimelineResponse>(TIMELINE_URL, {
method: 'PATCH',
body: requestBody,
version: '2023-10-31',
@ -175,7 +156,7 @@ export const copyTimeline = async ({
timelineId,
timeline,
savedSearch,
}: RequestPersistTimeline): Promise<TimelineResponse | TimelineErrorResponse> => {
}: RequestPersistTimeline): Promise<CopyTimelineResponse | TimelineErrorResponse> => {
let response = null;
let requestBody = null;
let newSavedSearchId = null;
@ -205,7 +186,7 @@ export const copyTimeline = async ({
}
try {
response = await KibanaServices.get().http.post<TimelineResponse>(TIMELINE_COPY_URL, {
response = await KibanaServices.get().http.post<CopyTimelineResponse>(TIMELINE_COPY_URL, {
method: 'POST',
body: requestBody,
version: '1',
@ -224,10 +205,10 @@ export const persistTimeline = async ({
timeline,
version,
savedSearch,
}: RequestPersistTimeline): Promise<TimelineResponse | TimelineErrorResponse> => {
}: RequestPersistTimeline): Promise<PersistTimelineResponse | TimelineErrorResponse> => {
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<TimelineResponse> => {
const response = await KibanaServices.get().http.get<TimelineResponse>(TIMELINE_DRAFT_URL, {
query: {
timelineType,
},
version: '2023-10-31',
});
}): Promise<GetDraftTimelinesResponse> => {
const response = await KibanaServices.get().http.get<GetDraftTimelinesResponse>(
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<TimelineResponse | TimelineErrorResponse> => {
}): Promise<CleanDraftTimelinesResponse | TimelineErrorResponse> => {
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<TimelineResponse>(TIMELINE_DRAFT_URL, {
body: requestBody,
version: '2023-10-31',
});
const response = await KibanaServices.get().http.post<CleanDraftTimelinesResponse>(
TIMELINE_DRAFT_URL,
{
body: requestBody,
version: '2023-10-31',
}
);
return decodeTimelineResponse(response);
};
export const installPrepackedTimelines = async (): Promise<ImportTimelineResultSchema> => {
const response = await KibanaServices.get().http.post<ImportTimelineResultSchema>(
export const installPrepackedTimelines = async (): Promise<ImportTimelineResult> => {
const response = await KibanaServices.get().http.post<ImportTimelineResult>(
TIMELINE_PREPACKAGED_URL,
{
version: '2023-10-31',
@ -386,7 +373,7 @@ export const installPrepackedTimelines = async (): Promise<ImportTimelineResultS
};
export const getTimeline = async (id: string) => {
const response = await KibanaServices.get().http.get<SingleTimelineResponse>(TIMELINE_URL, {
const response = await KibanaServices.get().http.get<GetTimelineResponse>(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<SingleTimelineResolveResponse>(
const response = await KibanaServices.get().http.get<ResolveTimelineResponse>(
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<SingleTimelineResponse>(TIMELINE_URL, {
const response = await KibanaServices.get().http.get<GetTimelineResponse>(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<AllTimelinesResponse>(TIMELINES_URL, {
export const getAllTimelines = async (
query: GetTimelinesRequestQuery,
abortSignal: AbortSignal
) => {
const response = await KibanaServices.get().http.fetch<GetTimelinesResponse>(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 ({

View file

@ -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 &&

View file

@ -66,6 +66,7 @@ describe('Timeline pinned event middleware', () => {
data: {
persistPinnedEventOnTimeline: {
code: 200,
eventId: testEventId,
},
},
});

View file

@ -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,

View file

@ -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',

View file

@ -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<TimelineInput>((acc, key) => {
): SavedTimeline =>
Object.keys(timelineInput).reduce<SavedTimeline>((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);

View file

@ -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,

View file

@ -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),
};
};

View file

@ -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<typeof createTimelineSchema>) =>
export const getCreateTimelinesRequest = (mockBody: CreateTimelinesRequestBody) =>
requestMock.create({
method: 'post',
path: TIMELINE_URL,
body: mockBody,
});
export const getUpdateTimelinesRequest = (mockBody: rt.TypeOf<typeof patchTimelineSchema>) =>
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,

View file

@ -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',

View file

@ -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<IKibanaResponse<CleanDraftTimelinesResponse>> => {
const frameworkRequest = await buildFrameworkRequest(context, request);
const siemResponse = buildSiemResponse(response);

View file

@ -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<IKibanaResponse<GetDraftTimelinesResponse>> => {
const frameworkRequest = await buildFrameworkRequest(context, request);
const siemResponse = buildSiemResponse(response);

View file

@ -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);
}

View file

@ -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<IKibanaResponse<DeleteNoteResponse>> => {
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);

View file

@ -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<IKibanaResponse<GetNotesResponse>> => {
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;

View file

@ -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<IKibanaResponse<PersistNoteRouteResponse>> => {
const siemResponse = buildSiemResponse(response);
try {

View file

@ -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<IKibanaResponse<PersistPinnedEventRouteResponse>> => {
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);

View file

@ -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();
});
});

View file

@ -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<ImportTimelineResultSchema | Error> => {
): Promise<ImportTimelineResult | Error> => {
let readStream;
const dir = resolve(
join(
@ -47,7 +47,7 @@ export const installPrepackagedTimelines = async (
],
};
}
return loadData<null, ImportTimelineResultSchema>(readStream, <T>(docs: T) =>
return loadData<null, ImportTimelineResult>(readStream, <T>(docs: T) =>
docs instanceof Readable
? importTimelines(docs, maxTimelineImportExportSize, frameworkRequest, isImmutable)
: Promise.reject(new Error(`read prepackaged timelines error`))

View file

@ -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<IKibanaResponse<InstallPrepackedTimelinesResponse>> => {
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) {

View file

@ -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<IKibanaResponse<CopyTimelineResponse>> => {
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 } },
});

View file

@ -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<ResponseTimeline> => {
}: CreateTimelineProps): Promise<InternalTimelineResponse> => {
const timerangeStart = isImmutable
? moment().subtract(24, 'hours').toISOString()
: timeline.dateRange?.start;

View file

@ -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),
},
},
},

View file

@ -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<IKibanaResponse<DeleteTimelinesResponse>> => {
const siemResponse = buildSiemResponse(response);
try {

View file

@ -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<Array<ExportedTimelines | ExportTimelineNotFoundError>> => {
): Promise<Array<TimelineResponse | ExportTimelineNotFoundError>> => {
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<ExportedTimelines[]>((acc, timelineId) => {
const myResponse = exportedIds.reduce<TimelineResponse[]>((acc, timelineId) => {
const myTimeline = timelines.find((t) => t.savedObjectId === timelineId);
if (myTimeline != null) {
const timelineNotes = myNotes.filter((n) => n.timelineId === timelineId);

View file

@ -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 () => {

View file

@ -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<IKibanaResponse<GetTimelineResponse>> => {
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);

View file

@ -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 () => {

View file

@ -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<IKibanaResponse<GetTimelinesResponse>> => {
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
? {

View file

@ -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 =
<A, O, I>(runtimeType: rt.Type<A, O, I>, 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 [

View file

@ -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<ImportTimelineResultSchema | Error> => {
): Promise<ImportTimelineResult | Error> => {
const readStream = createTimelinesStreamFromNdJson(maxTimelineImportExportSize);
const parsedObjects = await createPromiseFromStreams<PromiseFromStreams[]>([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');
}
};

View file

@ -39,7 +39,6 @@ import {
describe('import timelines', () => {
let server: ReturnType<typeof serverMock.create>;
let request: ReturnType<typeof requestMock.create>;
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"
);
});
});

View file

@ -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<IKibanaResponse<ImportTimelinesResponse>> => {
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');
}

View file

@ -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<IKibanaResponse<PatchTimelinesResponse>> => {
async (context, request, response): Promise<IKibanaResponse<PatchTimelineResponse>> => {
const siemResponse = buildSiemResponse(response);
try {

View file

@ -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<IKibanaResponse<PersistFavoriteRouteResponse>> => {
const siemResponse = buildSiemResponse(response);
try {

View file

@ -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<IKibanaResponse<ResolveTimelineResponse>> => {
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

View file

@ -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
) => {

View file

@ -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) => {

View file

@ -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);

View file

@ -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<SavedTimeline, 'dataViewId' | 'savedQueryId'>;
export const getTimeline = async (
request: FrameworkRequest,
timelineId: string,
timelineType: TimelineType | null = TimelineTypeEnum.default
): Promise<TimelineSavedObject> => {
): Promise<TimelineResponse> => {
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<TimelineSavedObject | null> => {
): Promise<TimelineResponse | null> => {
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<ResolvedTimelineWithOutcomeSavedObject | null> => {
let resolvedTimeline = null;
): Promise<ResolvedTimeline | null> => {
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<GetTimelinesResponse> => {
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<TimelineSavedObject | null> => {
): Promise<TimelineResponse | null> => {
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<GetTimelinesResponse> => {
const queryPageInfo =
countsOnly && pageInfo == null
? {
@ -218,7 +210,7 @@ export const getAllTimeline = async (
sort: SortTimeline | null,
status: TimelineStatus | null,
timelineType: TimelineType | null
): Promise<AllTimelinesResponse> => {
): Promise<GetTimelinesResponse> => {
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<ResponseTimelines> => {
): Promise<GetTimelinesResponse> => {
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<ResponseTimeline> => {
): Promise<InternalTimelineResponse> => {
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<InternalTimelineResponse> => {
const { transformedFields: migratedAttributes, references } =
timelineFieldsMigrator.extractFieldsToReferences<TimelineWithoutExternalRefs>({
data: pickSavedTimeline(timelineId, timeline, userInfo),
@ -479,7 +477,7 @@ const updateTimeline = async ({
savedObjectsClient: SavedObjectsClientContract;
userInfo: AuthenticatedUser | null;
version: string | null;
}) => {
}): Promise<InternalTimelineResponse> => {
const rawTimelineSavedObject =
await savedObjectsClient.get<SavedObjectTimelineWithoutExternalRefs>(
timelineSavedObjectType,
@ -516,11 +514,12 @@ export const updatePartialSavedTimeline = async (
timelineId
);
const { transformedFields, references } =
timelineFieldsMigrator.extractFieldsToReferences<TimelineWithoutExternalRefs>({
data: timeline,
existingReferences: currentSavedTimeline.references,
});
const { transformedFields, references } = timelineFieldsMigrator.extractFieldsToReferences<
Omit<SavedTimelineWithSavedObjectId, 'dataViewId' | 'savedQueryId'>
>({
data: timeline,
existingReferences: currentSavedTimeline.references,
});
const timelineUpdateAttributes = pickSavedTimeline(
null,
@ -588,7 +587,7 @@ export const copyTimeline = async (
request: FrameworkRequest,
timeline: SavedTimeline,
timelineId: string
): Promise<ResponseTimeline> => {
): Promise<InternalTimelineResponse> => {
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<ResolvedTimeline> => {
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[],
}
);

View file

@ -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) {

View file

@ -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<CheckTimelineStatusRt | Error> => {
): Promise<InstallPrepackedTimelinesRequestBody | Error> => {
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,
<T>(timelinesFromFileSystem: T) => {
if (Array.isArray(timelinesFromFileSystem)) {

View file

@ -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<Readable> =>
new Promise((resolved, reject) => {
const contents: string[] = [];

View file

@ -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,

View file

@ -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 {

Some files were not shown because too many files have changed in this diff Show more