mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[ResponseOps][Rules] Delete legacy routes (#203148)
## Summary Resolves https://github.com/elastic/kibana/issues/195179 Resolves https://github.com/elastic/kibana/issues/192558 This PR deletes deprecated legacy alerts routes `api/alerts/alert` in v9.0. It also updates docs to reflect the same. ### Checklist - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials ### Release notes Deleted deprecated alerts routes. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
3a6d27af37
commit
279f4aec6f
82 changed files with 36 additions and 9761 deletions
|
@ -4521,724 +4521,6 @@ paths:
|
|||
summary: Get information about rules
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerts/alert/{alertId}:
|
||||
delete:
|
||||
deprecated: true
|
||||
description: |
|
||||
Deprecated in 7.13.0. Use the delete rule API instead. WARNING: After you delete an alert, you cannot recover it.
|
||||
operationId: legaryDeleteAlert
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/Alerting_kbn_xsrf'
|
||||
- description: The identifier for the alert.
|
||||
in: path
|
||||
name: alertId
|
||||
required: true
|
||||
schema:
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Delete an alert
|
||||
tags:
|
||||
- alerting
|
||||
get:
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the get rule API instead.
|
||||
operationId: legacyGetAlert
|
||||
parameters:
|
||||
- description: The identifier for the alert.
|
||||
in: path
|
||||
name: alertId
|
||||
required: true
|
||||
schema:
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_alert_response_properties'
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Get an alert by identifier
|
||||
tags:
|
||||
- alerting
|
||||
post:
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the create rule API instead.
|
||||
operationId: legacyCreateAlert
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/Alerting_kbn_xsrf'
|
||||
- description: An UUID v1 or v4 identifier for the alert. If this parameter is omitted, the identifier is randomly generated.
|
||||
in: path
|
||||
name: alertId
|
||||
required: true
|
||||
schema:
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
title: Legacy create alert request properties
|
||||
type: object
|
||||
properties:
|
||||
actions:
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
actionTypeId:
|
||||
description: The identifier for the action type.
|
||||
type: string
|
||||
group:
|
||||
description: |
|
||||
Grouping actions is recommended for escalations for different types of alert instances. If you don't need this functionality, set it to `default`.
|
||||
type: string
|
||||
id:
|
||||
description: The ID of the action saved object.
|
||||
type: string
|
||||
params:
|
||||
description: |
|
||||
The map to the `params` that the action type will receive. `params` are handled as Mustache templates and passed a default set of context.
|
||||
type: object
|
||||
required:
|
||||
- actionTypeId
|
||||
- group
|
||||
- id
|
||||
- params
|
||||
type: array
|
||||
alertTypeId:
|
||||
description: The ID of the alert type that you want to call when the alert is scheduled to run.
|
||||
type: string
|
||||
consumer:
|
||||
description: The name of the application that owns the alert. This name has to match the Kibana feature name, as that dictates the required role-based access control privileges.
|
||||
type: string
|
||||
enabled:
|
||||
description: Indicates if you want to run the alert on an interval basis after it is created.
|
||||
type: boolean
|
||||
name:
|
||||
description: A name to reference and search.
|
||||
type: string
|
||||
notifyWhen:
|
||||
description: The condition for throttling the notification.
|
||||
enum:
|
||||
- onActionGroupChange
|
||||
- onActiveAlert
|
||||
- onThrottleInterval
|
||||
type: string
|
||||
params:
|
||||
description: The parameters to pass to the alert type executor `params` value. This will also validate against the alert type params validator, if defined.
|
||||
type: object
|
||||
schedule:
|
||||
description: |
|
||||
The schedule specifying when this alert should be run. A schedule is structured such that the key specifies the format you wish to use and its value specifies the schedule.
|
||||
type: object
|
||||
properties:
|
||||
interval:
|
||||
description: The interval format specifies the interval in seconds, minutes, hours or days at which the alert should run.
|
||||
example: 10s
|
||||
type: string
|
||||
tags:
|
||||
description: A list of keywords to reference and search.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
throttle:
|
||||
description: |
|
||||
How often this alert should fire the same actions. This will prevent the alert from sending out the same notification over and over. For example, if an alert with a schedule of 1 minute stays in a triggered state for 90 minutes, setting a throttle of `10m` or `1h` will prevent it from sending 90 notifications during this period.
|
||||
type: string
|
||||
required:
|
||||
- alertTypeId
|
||||
- consumer
|
||||
- name
|
||||
- notifyWhen
|
||||
- params
|
||||
- schedule
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_alert_response_properties'
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Create an alert
|
||||
tags:
|
||||
- alerting
|
||||
put:
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the update rule API instead.
|
||||
operationId: legacyUpdateAlert
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/Alerting_kbn_xsrf'
|
||||
- description: The identifier for the alert.
|
||||
in: path
|
||||
name: alertId
|
||||
required: true
|
||||
schema:
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
title: Legacy update alert request properties
|
||||
type: object
|
||||
properties:
|
||||
actions:
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
actionTypeId:
|
||||
description: The identifier for the action type.
|
||||
type: string
|
||||
group:
|
||||
description: |
|
||||
Grouping actions is recommended for escalations for different types of alert instances. If you don't need this functionality, set it to `default`.
|
||||
type: string
|
||||
id:
|
||||
description: The ID of the action saved object.
|
||||
type: string
|
||||
params:
|
||||
description: |
|
||||
The map to the `params` that the action type will receive. `params` are handled as Mustache templates and passed a default set of context.
|
||||
type: object
|
||||
required:
|
||||
- actionTypeId
|
||||
- group
|
||||
- id
|
||||
- params
|
||||
type: array
|
||||
name:
|
||||
description: A name to reference and search.
|
||||
type: string
|
||||
notifyWhen:
|
||||
description: The condition for throttling the notification.
|
||||
enum:
|
||||
- onActionGroupChange
|
||||
- onActiveAlert
|
||||
- onThrottleInterval
|
||||
type: string
|
||||
params:
|
||||
description: The parameters to pass to the alert type executor `params` value. This will also validate against the alert type params validator, if defined.
|
||||
type: object
|
||||
schedule:
|
||||
description: |
|
||||
The schedule specifying when this alert should be run. A schedule is structured such that the key specifies the format you wish to use and its value specifies the schedule.
|
||||
type: object
|
||||
properties:
|
||||
interval:
|
||||
description: The interval format specifies the interval in seconds, minutes, hours or days at which the alert should run.
|
||||
example: 1d
|
||||
type: string
|
||||
tags:
|
||||
description: A list of keywords to reference and search.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
throttle:
|
||||
description: |
|
||||
How often this alert should fire the same actions. This will prevent the alert from sending out the same notification over and over. For example, if an alert with a schedule of 1 minute stays in a triggered state for 90 minutes, setting a throttle of `10m` or `1h` will prevent it from sending 90 notifications during this period.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- notifyWhen
|
||||
- params
|
||||
- schedule
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_alert_response_properties'
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Update an alert
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerts/alert/{alertId}/_disable:
|
||||
post:
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the disable rule API instead.
|
||||
operationId: legacyDisableAlert
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/Alerting_kbn_xsrf'
|
||||
- description: The identifier for the alert.
|
||||
in: path
|
||||
name: alertId
|
||||
required: true
|
||||
schema:
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Disable an alert
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerts/alert/{alertId}/_enable:
|
||||
post:
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the enable rule API instead.
|
||||
operationId: legacyEnableAlert
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/Alerting_kbn_xsrf'
|
||||
- description: The identifier for the alert.
|
||||
in: path
|
||||
name: alertId
|
||||
required: true
|
||||
schema:
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Enable an alert
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerts/alert/{alertId}/_mute_all:
|
||||
post:
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the mute all alerts API instead.
|
||||
operationId: legacyMuteAllAlertInstances
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/Alerting_kbn_xsrf'
|
||||
- description: The identifier for the alert.
|
||||
in: path
|
||||
name: alertId
|
||||
required: true
|
||||
schema:
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Mute all alert instances
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerts/alert/{alertId}/_unmute_all:
|
||||
post:
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the unmute all alerts API instead.
|
||||
operationId: legacyUnmuteAllAlertInstances
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/Alerting_kbn_xsrf'
|
||||
- description: The identifier for the alert.
|
||||
in: path
|
||||
name: alertId
|
||||
required: true
|
||||
schema:
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Unmute all alert instances
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_mute:
|
||||
post:
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the mute alert API instead.
|
||||
operationId: legacyMuteAlertInstance
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/Alerting_kbn_xsrf'
|
||||
- description: An identifier for the alert.
|
||||
in: path
|
||||
name: alertId
|
||||
required: true
|
||||
schema:
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
type: string
|
||||
- description: An identifier for the alert instance.
|
||||
in: path
|
||||
name: alertInstanceId
|
||||
required: true
|
||||
schema:
|
||||
example: dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Mute an alert instance
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute:
|
||||
post:
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the unmute alert API instead.
|
||||
operationId: legacyUnmuteAlertInstance
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/Alerting_kbn_xsrf'
|
||||
- description: An identifier for the alert.
|
||||
in: path
|
||||
name: alertId
|
||||
required: true
|
||||
schema:
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
type: string
|
||||
- description: An identifier for the alert instance.
|
||||
in: path
|
||||
name: alertInstanceId
|
||||
required: true
|
||||
schema:
|
||||
example: dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Unmute an alert instance
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerts/alerts/_find:
|
||||
get:
|
||||
deprecated: true
|
||||
description: |
|
||||
Deprecated in 7.13.0. Use the find rules API instead. NOTE: Alert `params` are stored as a flattened field type and analyzed as keywords. As alerts change in Kibana, the results on each page of the response also change. Use the find API for traditional paginated results, but avoid using it to export large amounts of data.
|
||||
operationId: legacyFindAlerts
|
||||
parameters:
|
||||
- description: The default operator to use for the `simple_query_string`.
|
||||
example: OR
|
||||
in: query
|
||||
name: default_search_operator
|
||||
schema:
|
||||
default: OR
|
||||
type: string
|
||||
- description: The fields to return in the `attributes` key of the response.
|
||||
in: query
|
||||
name: fields
|
||||
schema:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
- description: |
|
||||
A KQL string that you filter with an attribute from your saved object. It should look like `savedObjectType.attributes.title: "myTitle"`. However, if you used a direct attribute of a saved object, such as `updatedAt`, you must define your filter, for example, `savedObjectType.updatedAt > 2018-12-22`.
|
||||
in: query
|
||||
name: filter
|
||||
schema:
|
||||
type: string
|
||||
- description: Filters the rules that have a relation with the reference objects with a specific type and identifier.
|
||||
in: query
|
||||
name: has_reference
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
- description: The page number to return.
|
||||
example: 1
|
||||
in: query
|
||||
name: page
|
||||
schema:
|
||||
default: 1
|
||||
type: integer
|
||||
- description: The number of alerts to return per page.
|
||||
example: 20
|
||||
in: query
|
||||
name: per_page
|
||||
schema:
|
||||
default: 20
|
||||
type: integer
|
||||
- description: An Elasticsearch `simple_query_string` query that filters the alerts in the response.
|
||||
in: query
|
||||
name: search
|
||||
schema:
|
||||
type: string
|
||||
- description: The fields to perform the `simple_query_string` parsed query against.
|
||||
in: query
|
||||
name: search_fields
|
||||
schema:
|
||||
oneOf:
|
||||
- type: string
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
- description: |
|
||||
Determines which field is used to sort the results. The field must exist in the `attributes` key of the response.
|
||||
in: query
|
||||
name: sort_field
|
||||
schema:
|
||||
type: string
|
||||
- description: Determines the sort order.
|
||||
example: asc
|
||||
in: query
|
||||
name: sort_order
|
||||
schema:
|
||||
default: desc
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/components/schemas/Alerting_alert_response_properties'
|
||||
type: array
|
||||
page:
|
||||
type: integer
|
||||
perPage:
|
||||
type: integer
|
||||
total:
|
||||
type: integer
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Get a paginated set of alerts
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerts/alerts/_health:
|
||||
get:
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the get alerting framework health API instead.
|
||||
operationId: legacyGetAlertingHealth
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
alertingFrameworkHealth:
|
||||
description: |
|
||||
Three substates identify the health of the alerting framework: `decryptionHealth`, `executionHealth`, and `readHealth`.
|
||||
type: object
|
||||
properties:
|
||||
decryptionHealth:
|
||||
description: The timestamp and status of the alert decryption.
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
enum:
|
||||
- error
|
||||
- ok
|
||||
- warn
|
||||
example: ok
|
||||
type: string
|
||||
timestamp:
|
||||
example: '2023-01-13T01:28:00.280Z'
|
||||
format: date-time
|
||||
type: string
|
||||
executionHealth:
|
||||
description: The timestamp and status of the alert execution.
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
enum:
|
||||
- error
|
||||
- ok
|
||||
- warn
|
||||
example: ok
|
||||
type: string
|
||||
timestamp:
|
||||
example: '2023-01-13T01:28:00.280Z'
|
||||
format: date-time
|
||||
type: string
|
||||
readHealth:
|
||||
description: The timestamp and status of the alert reading events.
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
enum:
|
||||
- error
|
||||
- ok
|
||||
- warn
|
||||
example: ok
|
||||
type: string
|
||||
timestamp:
|
||||
example: '2023-01-13T01:28:00.280Z'
|
||||
format: date-time
|
||||
type: string
|
||||
hasPermanentEncryptionKey:
|
||||
description: If `false`, the encrypted saved object plugin does not have a permanent encryption key.
|
||||
example: true
|
||||
type: boolean
|
||||
isSufficientlySecure:
|
||||
description: If `false`, security is enabled but TLS is not.
|
||||
example: true
|
||||
type: boolean
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Get the alerting framework health
|
||||
tags:
|
||||
- alerting
|
||||
/api/alerts/alerts/list_alert_types:
|
||||
get:
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the get rule types API instead.
|
||||
operationId: legacyGetAlertTypes
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
actionGroups:
|
||||
description: |
|
||||
An explicit list of groups for which the alert type can schedule actions, each with the action group's unique ID and human readable name. Alert actions validation uses this configuration to ensure that groups are valid.
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: array
|
||||
actionVariables:
|
||||
description: |
|
||||
A list of action variables that the alert type makes available via context and state in action parameter templates, and a short human readable description. The Alert UI will use this information to prompt users for these variables in action parameter editors.
|
||||
type: object
|
||||
properties:
|
||||
context:
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: array
|
||||
params:
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: array
|
||||
state:
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: array
|
||||
authorizedConsumers:
|
||||
description: The list of the plugins IDs that have access to the alert type.
|
||||
type: object
|
||||
defaultActionGroupId:
|
||||
description: The default identifier for the alert type group.
|
||||
type: string
|
||||
enabledInLicense:
|
||||
description: Indicates whether the rule type is enabled based on the subscription.
|
||||
type: boolean
|
||||
id:
|
||||
description: The unique identifier for the alert type.
|
||||
type: string
|
||||
isExportable:
|
||||
description: Indicates whether the alert type is exportable in Saved Objects Management UI.
|
||||
type: boolean
|
||||
minimumLicenseRequired:
|
||||
description: The subscriptions required to use the alert type.
|
||||
type: string
|
||||
name:
|
||||
description: The descriptive name of the alert type.
|
||||
type: string
|
||||
producer:
|
||||
description: An identifier for the application that produces this alert type.
|
||||
type: string
|
||||
recoveryActionGroup:
|
||||
description: |
|
||||
An action group to use when an alert instance goes from an active state to an inactive one. If it is not specified, the default recovered action group is used.
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: array
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Alerting_401_response'
|
||||
description: Authorization information is missing or invalid.
|
||||
summary: Get the alert types
|
||||
tags:
|
||||
- alerting
|
||||
/api/apm/agent_keys:
|
||||
post:
|
||||
description: Create a new agent key for APM.
|
||||
|
@ -44044,13 +43326,6 @@ components:
|
|||
disabledFeatures: []
|
||||
imageUrl: ''
|
||||
parameters:
|
||||
Alerting_kbn_xsrf:
|
||||
description: Cross-site request forgery protection
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
APM_UI_elastic_api_version:
|
||||
description: The version of the API to use
|
||||
in: header
|
||||
|
@ -44404,89 +43679,6 @@ components:
|
|||
type: integer
|
||||
title: Unsuccessful rule API response
|
||||
type: object
|
||||
Alerting_alert_response_properties:
|
||||
title: Legacy alert response properties
|
||||
type: object
|
||||
properties:
|
||||
actions:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
alertTypeId:
|
||||
example: .index-threshold
|
||||
type: string
|
||||
apiKeyOwner:
|
||||
example: elastic
|
||||
nullable: true
|
||||
type: string
|
||||
createdAt:
|
||||
description: The date and time that the alert was created.
|
||||
example: '2022-12-05T23:36:58.284Z'
|
||||
format: date-time
|
||||
type: string
|
||||
createdBy:
|
||||
description: The identifier for the user that created the alert.
|
||||
example: elastic
|
||||
type: string
|
||||
enabled:
|
||||
description: Indicates whether the alert is currently enabled.
|
||||
example: true
|
||||
type: boolean
|
||||
executionStatus:
|
||||
type: object
|
||||
properties:
|
||||
lastExecutionDate:
|
||||
example: '2022-12-06T00:13:43.890Z'
|
||||
format: date-time
|
||||
type: string
|
||||
status:
|
||||
example: ok
|
||||
type: string
|
||||
id:
|
||||
description: The identifier for the alert.
|
||||
example: b530fed0-74f5-11ed-9801-35303b735aef
|
||||
type: string
|
||||
muteAll:
|
||||
example: false
|
||||
type: boolean
|
||||
mutedInstanceIds:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
name:
|
||||
description: The name of the alert.
|
||||
example: my alert
|
||||
type: string
|
||||
notifyWhen:
|
||||
example: onActionGroupChange
|
||||
type: string
|
||||
params:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
schedule:
|
||||
type: object
|
||||
properties:
|
||||
interval:
|
||||
type: string
|
||||
scheduledTaskId:
|
||||
example: b530fed0-74f5-11ed-9801-35303b735aef
|
||||
type: string
|
||||
tags:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
throttle:
|
||||
nullable: true
|
||||
type: string
|
||||
updatedAt:
|
||||
example: '2022-12-05T23:36:58.284Z'
|
||||
type: string
|
||||
updatedBy:
|
||||
description: The identifier for the user that updated this alert most recently.
|
||||
example: elastic
|
||||
nullable: true
|
||||
type: string
|
||||
Alerting_fieldmap_properties:
|
||||
title: Field map objects in the get rule types response
|
||||
type: object
|
||||
|
|
|
@ -33,7 +33,7 @@ export interface QueryParams {
|
|||
to: string | null;
|
||||
}
|
||||
|
||||
const LEGACY_BASE_ALERT_API_PATH = '/api/alerts';
|
||||
const BASE_ALERTING_API_PATH = '/api/alerting';
|
||||
|
||||
const buildTimeRangeFilter = (
|
||||
dataView: DataView,
|
||||
|
@ -77,7 +77,7 @@ export const getAlertUtils = (
|
|||
const fetchAlert = async (id: string) => {
|
||||
try {
|
||||
return await core.http.get<Rule<SearchThresholdAlertParams>>(
|
||||
`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`
|
||||
`${BASE_ALERTING_API_PATH}/rule/${id}`
|
||||
);
|
||||
} catch (error) {
|
||||
const errorTitle = i18n.translate('discover.viewAlert.alertRuleFetchErrorTitle', {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -353,724 +353,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
/api/alerts/alert/{alertId}:
|
||||
delete:
|
||||
summary: Delete an alert
|
||||
operationId: legaryDeleteAlert
|
||||
deprecated: true
|
||||
description: |
|
||||
Deprecated in 7.13.0. Use the delete rule API instead. WARNING: After you delete an alert, you cannot recover it.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
get:
|
||||
summary: Get an alert by identifier
|
||||
operationId: legacyGetAlert
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the get rule API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/alert_response_properties'
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
post:
|
||||
summary: Create an alert
|
||||
operationId: legacyCreateAlert
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the create rule API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
- in: path
|
||||
name: alertId
|
||||
description: An UUID v1 or v4 identifier for the alert. If this parameter is omitted, the identifier is randomly generated.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
title: Legacy create alert request properties
|
||||
type: object
|
||||
required:
|
||||
- alertTypeId
|
||||
- consumer
|
||||
- name
|
||||
- notifyWhen
|
||||
- params
|
||||
- schedule
|
||||
properties:
|
||||
actions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- actionTypeId
|
||||
- group
|
||||
- id
|
||||
- params
|
||||
properties:
|
||||
actionTypeId:
|
||||
type: string
|
||||
description: The identifier for the action type.
|
||||
group:
|
||||
type: string
|
||||
description: |
|
||||
Grouping actions is recommended for escalations for different types of alert instances. If you don't need this functionality, set it to `default`.
|
||||
id:
|
||||
type: string
|
||||
description: The ID of the action saved object.
|
||||
params:
|
||||
type: object
|
||||
description: |
|
||||
The map to the `params` that the action type will receive. `params` are handled as Mustache templates and passed a default set of context.
|
||||
alertTypeId:
|
||||
type: string
|
||||
description: The ID of the alert type that you want to call when the alert is scheduled to run.
|
||||
consumer:
|
||||
type: string
|
||||
description: The name of the application that owns the alert. This name has to match the Kibana feature name, as that dictates the required role-based access control privileges.
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Indicates if you want to run the alert on an interval basis after it is created.
|
||||
name:
|
||||
type: string
|
||||
description: A name to reference and search.
|
||||
notifyWhen:
|
||||
type: string
|
||||
description: The condition for throttling the notification.
|
||||
enum:
|
||||
- onActionGroupChange
|
||||
- onActiveAlert
|
||||
- onThrottleInterval
|
||||
params:
|
||||
type: object
|
||||
description: The parameters to pass to the alert type executor `params` value. This will also validate against the alert type params validator, if defined.
|
||||
schedule:
|
||||
type: object
|
||||
description: |
|
||||
The schedule specifying when this alert should be run. A schedule is structured such that the key specifies the format you wish to use and its value specifies the schedule.
|
||||
properties:
|
||||
interval:
|
||||
type: string
|
||||
description: The interval format specifies the interval in seconds, minutes, hours or days at which the alert should run.
|
||||
example: 10s
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: A list of keywords to reference and search.
|
||||
throttle:
|
||||
type: string
|
||||
description: |
|
||||
How often this alert should fire the same actions. This will prevent the alert from sending out the same notification over and over. For example, if an alert with a schedule of 1 minute stays in a triggered state for 90 minutes, setting a throttle of `10m` or `1h` will prevent it from sending 90 notifications during this period.
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/alert_response_properties'
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
put:
|
||||
summary: Update an alert
|
||||
operationId: legacyUpdateAlert
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the update rule API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
title: Legacy update alert request properties
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- notifyWhen
|
||||
- params
|
||||
- schedule
|
||||
properties:
|
||||
actions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- actionTypeId
|
||||
- group
|
||||
- id
|
||||
- params
|
||||
properties:
|
||||
actionTypeId:
|
||||
type: string
|
||||
description: The identifier for the action type.
|
||||
group:
|
||||
type: string
|
||||
description: |
|
||||
Grouping actions is recommended for escalations for different types of alert instances. If you don't need this functionality, set it to `default`.
|
||||
id:
|
||||
type: string
|
||||
description: The ID of the action saved object.
|
||||
params:
|
||||
type: object
|
||||
description: |
|
||||
The map to the `params` that the action type will receive. `params` are handled as Mustache templates and passed a default set of context.
|
||||
name:
|
||||
type: string
|
||||
description: A name to reference and search.
|
||||
notifyWhen:
|
||||
type: string
|
||||
description: The condition for throttling the notification.
|
||||
enum:
|
||||
- onActionGroupChange
|
||||
- onActiveAlert
|
||||
- onThrottleInterval
|
||||
params:
|
||||
type: object
|
||||
description: The parameters to pass to the alert type executor `params` value. This will also validate against the alert type params validator, if defined.
|
||||
schedule:
|
||||
type: object
|
||||
description: |
|
||||
The schedule specifying when this alert should be run. A schedule is structured such that the key specifies the format you wish to use and its value specifies the schedule.
|
||||
properties:
|
||||
interval:
|
||||
type: string
|
||||
description: The interval format specifies the interval in seconds, minutes, hours or days at which the alert should run.
|
||||
example: 1d
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: A list of keywords to reference and search.
|
||||
throttle:
|
||||
type: string
|
||||
description: |
|
||||
How often this alert should fire the same actions. This will prevent the alert from sending out the same notification over and over. For example, if an alert with a schedule of 1 minute stays in a triggered state for 90 minutes, setting a throttle of `10m` or `1h` will prevent it from sending 90 notifications during this period.
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/alert_response_properties'
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
/api/alerts/alert/{alertId}/_disable:
|
||||
post:
|
||||
summary: Disable an alert
|
||||
operationId: legacyDisableAlert
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the disable rule API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
/api/alerts/alert/{alertId}/_enable:
|
||||
post:
|
||||
summary: Enable an alert
|
||||
operationId: legacyEnableAlert
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the enable rule API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
/api/alerts/alert/{alertId}/_mute_all:
|
||||
post:
|
||||
summary: Mute all alert instances
|
||||
operationId: legacyMuteAllAlertInstances
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the mute all alerts API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
/api/alerts/alert/{alertId}/_unmute_all:
|
||||
post:
|
||||
summary: Unmute all alert instances
|
||||
operationId: legacyUnmuteAllAlertInstances
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the unmute all alerts API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
/api/alerts/alerts/_find:
|
||||
get:
|
||||
summary: Get a paginated set of alerts
|
||||
operationId: legacyFindAlerts
|
||||
deprecated: true
|
||||
description: |
|
||||
Deprecated in 7.13.0. Use the find rules API instead. NOTE: Alert `params` are stored as a flattened field type and analyzed as keywords. As alerts change in Kibana, the results on each page of the response also change. Use the find API for traditional paginated results, but avoid using it to export large amounts of data.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- name: default_search_operator
|
||||
in: query
|
||||
description: The default operator to use for the `simple_query_string`.
|
||||
schema:
|
||||
type: string
|
||||
default: OR
|
||||
example: OR
|
||||
- name: fields
|
||||
in: query
|
||||
description: The fields to return in the `attributes` key of the response.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
- name: filter
|
||||
in: query
|
||||
description: |
|
||||
A KQL string that you filter with an attribute from your saved object. It should look like `savedObjectType.attributes.title: "myTitle"`. However, if you used a direct attribute of a saved object, such as `updatedAt`, you must define your filter, for example, `savedObjectType.updatedAt > 2018-12-22`.
|
||||
schema:
|
||||
type: string
|
||||
- name: has_reference
|
||||
in: query
|
||||
description: Filters the rules that have a relation with the reference objects with a specific type and identifier.
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
- name: page
|
||||
in: query
|
||||
description: The page number to return.
|
||||
schema:
|
||||
type: integer
|
||||
default: 1
|
||||
example: 1
|
||||
- name: per_page
|
||||
in: query
|
||||
description: The number of alerts to return per page.
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
example: 20
|
||||
- name: search
|
||||
in: query
|
||||
description: An Elasticsearch `simple_query_string` query that filters the alerts in the response.
|
||||
schema:
|
||||
type: string
|
||||
- name: search_fields
|
||||
in: query
|
||||
description: The fields to perform the `simple_query_string` parsed query against.
|
||||
schema:
|
||||
oneOf:
|
||||
- type: string
|
||||
- type: array
|
||||
items:
|
||||
type: string
|
||||
- name: sort_field
|
||||
in: query
|
||||
description: |
|
||||
Determines which field is used to sort the results. The field must exist in the `attributes` key of the response.
|
||||
schema:
|
||||
type: string
|
||||
- name: sort_order
|
||||
in: query
|
||||
description: Determines the sort order.
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
default: desc
|
||||
example: asc
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/alert_response_properties'
|
||||
page:
|
||||
type: integer
|
||||
perPage:
|
||||
type: integer
|
||||
total:
|
||||
type: integer
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
/api/alerts/alerts/_health:
|
||||
get:
|
||||
summary: Get the alerting framework health
|
||||
operationId: legacyGetAlertingHealth
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the get alerting framework health API instead.
|
||||
tags:
|
||||
- alerting
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
alertingFrameworkHealth:
|
||||
type: object
|
||||
description: |
|
||||
Three substates identify the health of the alerting framework: `decryptionHealth`, `executionHealth`, and `readHealth`.
|
||||
properties:
|
||||
decryptionHealth:
|
||||
type: object
|
||||
description: The timestamp and status of the alert decryption.
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: ok
|
||||
enum:
|
||||
- error
|
||||
- ok
|
||||
- warn
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2023-01-13T01:28:00.280Z'
|
||||
executionHealth:
|
||||
type: object
|
||||
description: The timestamp and status of the alert execution.
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: ok
|
||||
enum:
|
||||
- error
|
||||
- ok
|
||||
- warn
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2023-01-13T01:28:00.280Z'
|
||||
readHealth:
|
||||
type: object
|
||||
description: The timestamp and status of the alert reading events.
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: ok
|
||||
enum:
|
||||
- error
|
||||
- ok
|
||||
- warn
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2023-01-13T01:28:00.280Z'
|
||||
hasPermanentEncryptionKey:
|
||||
type: boolean
|
||||
description: If `false`, the encrypted saved object plugin does not have a permanent encryption key.
|
||||
example: true
|
||||
isSufficientlySecure:
|
||||
type: boolean
|
||||
description: If `false`, security is enabled but TLS is not.
|
||||
example: true
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
/api/alerts/alerts/list_alert_types:
|
||||
get:
|
||||
summary: Get the alert types
|
||||
operationId: legacyGetAlertTypes
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the get rule types API instead.
|
||||
tags:
|
||||
- alerting
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
actionGroups:
|
||||
description: |
|
||||
An explicit list of groups for which the alert type can schedule actions, each with the action group's unique ID and human readable name. Alert actions validation uses this configuration to ensure that groups are valid.
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
actionVariables:
|
||||
description: |
|
||||
A list of action variables that the alert type makes available via context and state in action parameter templates, and a short human readable description. The Alert UI will use this information to prompt users for these variables in action parameter editors.
|
||||
type: object
|
||||
properties:
|
||||
context:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
params:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
state:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
authorizedConsumers:
|
||||
description: The list of the plugins IDs that have access to the alert type.
|
||||
type: object
|
||||
defaultActionGroupId:
|
||||
description: The default identifier for the alert type group.
|
||||
type: string
|
||||
enabledInLicense:
|
||||
description: Indicates whether the rule type is enabled based on the subscription.
|
||||
type: boolean
|
||||
id:
|
||||
description: The unique identifier for the alert type.
|
||||
type: string
|
||||
isExportable:
|
||||
description: Indicates whether the alert type is exportable in Saved Objects Management UI.
|
||||
type: boolean
|
||||
minimumLicenseRequired:
|
||||
description: The subscriptions required to use the alert type.
|
||||
type: string
|
||||
name:
|
||||
description: The descriptive name of the alert type.
|
||||
type: string
|
||||
producer:
|
||||
description: An identifier for the application that produces this alert type.
|
||||
type: string
|
||||
recoveryActionGroup:
|
||||
description: |
|
||||
An action group to use when an alert instance goes from an active state to an inactive one. If it is not specified, the default recovered action group is used.
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_mute:
|
||||
post:
|
||||
summary: Mute an alert instance
|
||||
operationId: legacyMuteAlertInstance
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the mute alert API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
- in: path
|
||||
name: alertId
|
||||
description: An identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
- in: path
|
||||
name: alertInstanceId
|
||||
description: An identifier for the alert instance.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute:
|
||||
post:
|
||||
summary: Unmute an alert instance
|
||||
operationId: legacyUnmuteAlertInstance
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the unmute alert API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
- in: path
|
||||
name: alertId
|
||||
description: An identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
- in: path
|
||||
name: alertInstanceId
|
||||
description: An identifier for the alert instance.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401_response'
|
||||
components:
|
||||
examples:
|
||||
get_health_response:
|
||||
|
@ -1370,94 +652,3 @@ components:
|
|||
type: string
|
||||
description: Specifies the data type for the field.
|
||||
example: scaled_float
|
||||
alert_response_properties:
|
||||
title: Legacy alert response properties
|
||||
type: object
|
||||
properties:
|
||||
actions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
alertTypeId:
|
||||
type: string
|
||||
example: .index-threshold
|
||||
apiKeyOwner:
|
||||
type: string
|
||||
nullable: true
|
||||
example: elastic
|
||||
createdAt:
|
||||
type: string
|
||||
description: The date and time that the alert was created.
|
||||
format: date-time
|
||||
example: '2022-12-05T23:36:58.284Z'
|
||||
createdBy:
|
||||
type: string
|
||||
description: The identifier for the user that created the alert.
|
||||
example: elastic
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Indicates whether the alert is currently enabled.
|
||||
example: true
|
||||
executionStatus:
|
||||
type: object
|
||||
properties:
|
||||
lastExecutionDate:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2022-12-06T00:13:43.890Z'
|
||||
status:
|
||||
type: string
|
||||
example: ok
|
||||
id:
|
||||
type: string
|
||||
description: The identifier for the alert.
|
||||
example: b530fed0-74f5-11ed-9801-35303b735aef
|
||||
muteAll:
|
||||
type: boolean
|
||||
example: false
|
||||
mutedInstanceIds:
|
||||
type: array
|
||||
nullable: true
|
||||
items:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description: The name of the alert.
|
||||
example: my alert
|
||||
notifyWhen:
|
||||
type: string
|
||||
example: onActionGroupChange
|
||||
params:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
schedule:
|
||||
type: object
|
||||
properties:
|
||||
interval:
|
||||
type: string
|
||||
scheduledTaskId:
|
||||
type: string
|
||||
example: b530fed0-74f5-11ed-9801-35303b735aef
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
throttle:
|
||||
type: string
|
||||
nullable: true
|
||||
updatedAt:
|
||||
type: string
|
||||
example: '2022-12-05T23:36:58.284Z'
|
||||
updatedBy:
|
||||
type: string
|
||||
description: The identifier for the user that updated this alert most recently.
|
||||
nullable: true
|
||||
example: elastic
|
||||
parameters:
|
||||
kbn_xsrf:
|
||||
schema:
|
||||
type: string
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
description: Cross-site request forgery protection
|
||||
required: true
|
||||
|
|
|
@ -15,24 +15,3 @@ paths:
|
|||
$ref: paths/api@alerting@_health.yaml
|
||||
'/api/alerting/rule_types':
|
||||
$ref: 'paths/api@alerting@rule_types.yaml'
|
||||
# Deprecated APIs
|
||||
'/api/alerts/alert/{alertId}':
|
||||
$ref: 'paths/api@alerts@alert@{alertid}.yaml'
|
||||
'/api/alerts/alert/{alertId}/_disable':
|
||||
$ref: 'paths/api@alerts@alert@{alertid}@_disable.yaml'
|
||||
'/api/alerts/alert/{alertId}/_enable':
|
||||
$ref: 'paths/api@alerts@alert@{alertid}@_enable.yaml'
|
||||
'/api/alerts/alert/{alertId}/_mute_all':
|
||||
$ref: 'paths/api@alerts@alert@{alertid}@_mute_all.yaml'
|
||||
'/api/alerts/alert/{alertId}/_unmute_all':
|
||||
$ref: 'paths/api@alerts@alert@{alertid}@_unmute_all.yaml'
|
||||
'/api/alerts/alerts/_find':
|
||||
$ref: 'paths/api@alerts@_find.yaml'
|
||||
'/api/alerts/alerts/_health':
|
||||
$ref: 'paths/api@alerts@_health.yaml'
|
||||
'/api/alerts/alerts/list_alert_types':
|
||||
$ref: 'paths/api@alerts@list_alert_types.yaml'
|
||||
'/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_mute':
|
||||
$ref: 'paths/api@alerts@alert@{alertid}@alert_instance@{alertinstanceid}@_mute.yaml'
|
||||
'/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute':
|
||||
$ref: 'paths/api@alerts@alert@{alertid}@alert_instance@{alertinstanceid}@_unmute.yaml'
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
get:
|
||||
summary: Get a paginated set of alerts
|
||||
operationId: legacyFindAlerts
|
||||
deprecated: true
|
||||
description: >
|
||||
Deprecated in 7.13.0. Use the find rules API instead.
|
||||
NOTE: Alert `params` are stored as a flattened field type and analyzed as keywords.
|
||||
As alerts change in Kibana, the results on each page of the response also change.
|
||||
Use the find API for traditional paginated results, but avoid using it to export large amounts of data.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- name: default_search_operator
|
||||
in: query
|
||||
description: The default operator to use for the `simple_query_string`.
|
||||
schema:
|
||||
type: string
|
||||
default: OR
|
||||
example: OR
|
||||
- name: fields
|
||||
in: query
|
||||
description: The fields to return in the `attributes` key of the response.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
- name: filter
|
||||
in: query
|
||||
description: >
|
||||
A KQL string that you filter with an attribute from your saved object.
|
||||
It should look like `savedObjectType.attributes.title: "myTitle"`.
|
||||
However, if you used a direct attribute of a saved object, such as
|
||||
`updatedAt`, you must define your filter, for example,
|
||||
`savedObjectType.updatedAt > 2018-12-22`.
|
||||
schema:
|
||||
type: string
|
||||
- name: has_reference
|
||||
in: query
|
||||
description: Filters the rules that have a relation with the reference objects with a specific type and identifier.
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
- name: page
|
||||
in: query
|
||||
description: The page number to return.
|
||||
schema:
|
||||
type: integer
|
||||
default: 1
|
||||
example: 1
|
||||
- name: per_page
|
||||
in: query
|
||||
description: The number of alerts to return per page.
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
example: 20
|
||||
- name: search
|
||||
in: query
|
||||
description: An Elasticsearch `simple_query_string` query that filters the alerts in the response.
|
||||
schema:
|
||||
type: string
|
||||
- name: search_fields
|
||||
in: query
|
||||
description: The fields to perform the `simple_query_string` parsed query against.
|
||||
schema:
|
||||
oneOf:
|
||||
- type: string
|
||||
- type: array
|
||||
items:
|
||||
type: string
|
||||
- name: sort_field
|
||||
in: query
|
||||
description: >
|
||||
Determines which field is used to sort the results. The field must exist
|
||||
in the `attributes` key of the response.
|
||||
schema:
|
||||
type: string
|
||||
- name: sort_order
|
||||
in: query
|
||||
description: Determines the sort order.
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
default: desc
|
||||
example: asc
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '../components/schemas/alert_response_properties.yaml'
|
||||
page:
|
||||
type: integer
|
||||
perPage:
|
||||
type: integer
|
||||
total:
|
||||
type: integer
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
|
@ -1,79 +0,0 @@
|
|||
get:
|
||||
summary: Get the alerting framework health
|
||||
operationId: legacyGetAlertingHealth
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the get alerting framework health API instead.
|
||||
tags:
|
||||
- alerting
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
alertingFrameworkHealth:
|
||||
type: object
|
||||
description: >
|
||||
Three substates identify the health of the alerting framework: `decryptionHealth`, `executionHealth`, and `readHealth`.
|
||||
properties:
|
||||
decryptionHealth:
|
||||
type: object
|
||||
description: The timestamp and status of the alert decryption.
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: ok
|
||||
enum:
|
||||
- error
|
||||
- ok
|
||||
- warn
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2023-01-13T01:28:00.280Z"
|
||||
executionHealth:
|
||||
type: object
|
||||
description: The timestamp and status of the alert execution.
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: ok
|
||||
enum:
|
||||
- error
|
||||
- ok
|
||||
- warn
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2023-01-13T01:28:00.280Z"
|
||||
readHealth:
|
||||
type: object
|
||||
description: The timestamp and status of the alert reading events.
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: ok
|
||||
enum:
|
||||
- error
|
||||
- ok
|
||||
- warn
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2023-01-13T01:28:00.280Z"
|
||||
hasPermanentEncryptionKey:
|
||||
type: boolean
|
||||
description: If `false`, the encrypted saved object plugin does not have a permanent encryption key.
|
||||
example: true
|
||||
isSufficientlySecure:
|
||||
type: boolean
|
||||
description: If `false`, security is enabled but TLS is not.
|
||||
example: true
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
|
@ -1,275 +0,0 @@
|
|||
delete:
|
||||
summary: Delete an alert
|
||||
operationId: legaryDeleteAlert
|
||||
deprecated: true
|
||||
description: >
|
||||
Deprecated in 7.13.0. Use the delete rule API instead.
|
||||
WARNING: After you delete an alert, you cannot recover it.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: ../components/headers/kbn_xsrf.yaml
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
||||
|
||||
get:
|
||||
summary: Get an alert by identifier
|
||||
operationId: legacyGetAlert
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the get rule API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/alert_response_properties.yaml'
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
||||
|
||||
post:
|
||||
summary: Create an alert
|
||||
operationId: legacyCreateAlert
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the create rule API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: ../components/headers/kbn_xsrf.yaml
|
||||
- in: path
|
||||
name: alertId
|
||||
description: An UUID v1 or v4 identifier for the alert. If this parameter is omitted, the identifier is randomly generated.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
title: Legacy create alert request properties
|
||||
type: object
|
||||
required:
|
||||
- alertTypeId
|
||||
- consumer
|
||||
- name
|
||||
- notifyWhen
|
||||
- params
|
||||
- schedule
|
||||
properties:
|
||||
actions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- actionTypeId
|
||||
- group
|
||||
- id
|
||||
- params
|
||||
properties:
|
||||
actionTypeId:
|
||||
type: string
|
||||
description: The identifier for the action type.
|
||||
group:
|
||||
type: string
|
||||
description: >
|
||||
Grouping actions is recommended for escalations for different types of alert instances.
|
||||
If you don't need this functionality, set it to `default`.
|
||||
id:
|
||||
type: string
|
||||
description: The ID of the action saved object.
|
||||
params:
|
||||
type: object
|
||||
description: >
|
||||
The map to the `params` that the action type will receive.
|
||||
`params` are handled as Mustache templates and passed a default set of context.
|
||||
alertTypeId:
|
||||
type: string
|
||||
description: The ID of the alert type that you want to call when the alert is scheduled to run.
|
||||
consumer:
|
||||
type: string
|
||||
description: The name of the application that owns the alert. This name has to match the Kibana feature name, as that dictates the required role-based access control privileges.
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Indicates if you want to run the alert on an interval basis after it is created.
|
||||
name:
|
||||
type: string
|
||||
description: A name to reference and search.
|
||||
notifyWhen:
|
||||
type: string
|
||||
description: The condition for throttling the notification.
|
||||
enum:
|
||||
- onActionGroupChange
|
||||
- onActiveAlert
|
||||
- onThrottleInterval
|
||||
params:
|
||||
type: object
|
||||
description: The parameters to pass to the alert type executor `params` value. This will also validate against the alert type params validator, if defined.
|
||||
schedule:
|
||||
type: object
|
||||
description: >
|
||||
The schedule specifying when this alert should be run.
|
||||
A schedule is structured such that the key specifies the format you wish to use and its value specifies the schedule.
|
||||
properties:
|
||||
interval:
|
||||
type: string
|
||||
description: The interval format specifies the interval in seconds, minutes, hours or days at which the alert should run.
|
||||
example: "10s"
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: A list of keywords to reference and search.
|
||||
throttle:
|
||||
type: string
|
||||
description: >
|
||||
How often this alert should fire the same actions.
|
||||
This will prevent the alert from sending out the same notification over and over.
|
||||
For example, if an alert with a schedule of 1 minute stays in a triggered state for 90 minutes,
|
||||
setting a throttle of `10m` or `1h` will prevent it from sending 90 notifications during this period.
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/alert_response_properties.yaml'
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
||||
|
||||
put:
|
||||
summary: Update an alert
|
||||
operationId: legacyUpdateAlert
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the update rule API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: ../components/headers/kbn_xsrf.yaml
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
title: Legacy update alert request properties
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- notifyWhen
|
||||
- params
|
||||
- schedule
|
||||
properties:
|
||||
actions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- actionTypeId
|
||||
- group
|
||||
- id
|
||||
- params
|
||||
properties:
|
||||
actionTypeId:
|
||||
type: string
|
||||
description: The identifier for the action type.
|
||||
group:
|
||||
type: string
|
||||
description: >
|
||||
Grouping actions is recommended for escalations for different types of alert instances.
|
||||
If you don't need this functionality, set it to `default`.
|
||||
id:
|
||||
type: string
|
||||
description: The ID of the action saved object.
|
||||
params:
|
||||
type: object
|
||||
description: >
|
||||
The map to the `params` that the action type will receive.
|
||||
`params` are handled as Mustache templates and passed a default set of context.
|
||||
name:
|
||||
type: string
|
||||
description: A name to reference and search.
|
||||
notifyWhen:
|
||||
type: string
|
||||
description: The condition for throttling the notification.
|
||||
enum:
|
||||
- onActionGroupChange
|
||||
- onActiveAlert
|
||||
- onThrottleInterval
|
||||
params:
|
||||
type: object
|
||||
description: The parameters to pass to the alert type executor `params` value. This will also validate against the alert type params validator, if defined.
|
||||
schedule:
|
||||
type: object
|
||||
description: >
|
||||
The schedule specifying when this alert should be run.
|
||||
A schedule is structured such that the key specifies the format you wish to use and its value specifies the schedule.
|
||||
properties:
|
||||
interval:
|
||||
type: string
|
||||
description: The interval format specifies the interval in seconds, minutes, hours or days at which the alert should run.
|
||||
example: "1d"
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: A list of keywords to reference and search.
|
||||
throttle:
|
||||
type: string
|
||||
description: >
|
||||
How often this alert should fire the same actions.
|
||||
This will prevent the alert from sending out the same notification over and over.
|
||||
For example, if an alert with a schedule of 1 minute stays in a triggered state for 90 minutes,
|
||||
setting a throttle of `10m` or `1h` will prevent it from sending 90 notifications during this period.
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/alert_response_properties.yaml'
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
|
@ -1,25 +0,0 @@
|
|||
post:
|
||||
summary: Disable an alert
|
||||
operationId: legacyDisableAlert
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the disable rule API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: ../components/headers/kbn_xsrf.yaml
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
|
@ -1,25 +0,0 @@
|
|||
post:
|
||||
summary: Enable an alert
|
||||
operationId: legacyEnableAlert
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the enable rule API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: ../components/headers/kbn_xsrf.yaml
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
|
@ -1,25 +0,0 @@
|
|||
post:
|
||||
summary: Mute all alert instances
|
||||
operationId: legacyMuteAllAlertInstances
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the mute all alerts API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: ../components/headers/kbn_xsrf.yaml
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
|
@ -1,25 +0,0 @@
|
|||
post:
|
||||
summary: Unmute all alert instances
|
||||
operationId: legacyUnmuteAllAlertInstances
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the unmute all alerts API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: ../components/headers/kbn_xsrf.yaml
|
||||
- in: path
|
||||
name: alertId
|
||||
description: The identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
|
@ -1,32 +0,0 @@
|
|||
post:
|
||||
summary: Mute an alert instance
|
||||
operationId: legacyMuteAlertInstance
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the mute alert API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: ../components/headers/kbn_xsrf.yaml
|
||||
- in: path
|
||||
name: alertId
|
||||
description: An identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
- in: path
|
||||
name: alertInstanceId
|
||||
description: An identifier for the alert instance.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
|
@ -1,32 +0,0 @@
|
|||
post:
|
||||
summary: Unmute an alert instance
|
||||
operationId: legacyUnmuteAlertInstance
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the unmute alert API instead.
|
||||
tags:
|
||||
- alerting
|
||||
parameters:
|
||||
- $ref: ../components/headers/kbn_xsrf.yaml
|
||||
- in: path
|
||||
name: alertId
|
||||
description: An identifier for the alert.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 41893910-6bca-11eb-9e0d-85d233e3ee35
|
||||
- in: path
|
||||
name: alertInstanceId
|
||||
description: An identifier for the alert instance.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2
|
||||
responses:
|
||||
'204':
|
||||
description: Indicates a successful call.
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
|
@ -1,107 +0,0 @@
|
|||
get:
|
||||
summary: Get the alert types
|
||||
operationId: legacyGetAlertTypes
|
||||
deprecated: true
|
||||
description: Deprecated in 7.13.0. Use the get rule types API instead.
|
||||
tags:
|
||||
- alerting
|
||||
responses:
|
||||
'200':
|
||||
description: Indicates a successful call.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
actionGroups:
|
||||
description: >
|
||||
An explicit list of groups for which the alert type can
|
||||
schedule actions, each with the action group's unique ID and
|
||||
human readable name. Alert actions validation uses this
|
||||
configuration to ensure that groups are valid.
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
actionVariables:
|
||||
description: >
|
||||
A list of action variables that the alert type makes available
|
||||
via context and state in action parameter templates, and a
|
||||
short human readable description. The Alert UI will use this
|
||||
information to prompt users for these variables in action
|
||||
parameter editors.
|
||||
type: object
|
||||
properties:
|
||||
context:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
params:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
state:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
authorizedConsumers:
|
||||
description: The list of the plugins IDs that have access to the alert type.
|
||||
type: object
|
||||
defaultActionGroupId:
|
||||
description: The default identifier for the alert type group.
|
||||
type: string
|
||||
enabledInLicense:
|
||||
description: Indicates whether the rule type is enabled based on the subscription.
|
||||
type: boolean
|
||||
id:
|
||||
description: The unique identifier for the alert type.
|
||||
type: string
|
||||
isExportable:
|
||||
description: Indicates whether the alert type is exportable in Saved Objects Management UI.
|
||||
type: boolean
|
||||
minimumLicenseRequired:
|
||||
description: The subscriptions required to use the alert type.
|
||||
type: string
|
||||
name:
|
||||
description: The descriptive name of the alert type.
|
||||
type: string
|
||||
producer:
|
||||
description: An identifier for the application that produces this alert type.
|
||||
type: string
|
||||
recoveryActionGroup:
|
||||
description: >
|
||||
An action group to use when an alert instance goes from an active state to an inactive one.
|
||||
If it is not specified, the default recovered action group is used.
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
'401':
|
||||
description: Authorization information is missing or invalid.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
|
@ -11,7 +11,6 @@ import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-p
|
|||
import type { ConfigSchema } from '@kbn/unified-search-plugin/server/config';
|
||||
import { Observable } from 'rxjs';
|
||||
import { GetAlertIndicesAlias, ILicenseState } from '../lib';
|
||||
import { defineLegacyRoutes } from './legacy';
|
||||
import { AlertingRequestHandlerContext } from '../types';
|
||||
import { createRuleRoute } from './rule/apis/create';
|
||||
import { getRuleRoute, getInternalRuleRoute } from './rule/apis/get/get_rule_route';
|
||||
|
@ -94,10 +93,6 @@ export function defineRoutes(opts: RouteOptions) {
|
|||
getAlertIndicesAlias,
|
||||
} = opts;
|
||||
|
||||
// Legacy APIs
|
||||
defineLegacyRoutes(opts);
|
||||
|
||||
// Rule APIs
|
||||
createRuleRoute(opts);
|
||||
getRuleRoute(router, licenseState);
|
||||
getInternalRuleRoute(router, licenseState);
|
||||
|
|
|
@ -1,617 +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 { createAlertRoute } from './create';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { Rule, RuleSystemAction } from '../../../common/rule';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('createAlertRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
const createdAt = new Date();
|
||||
const updatedAt = new Date();
|
||||
|
||||
const mockedAlert = {
|
||||
alertTypeId: '1',
|
||||
consumer: 'bar',
|
||||
name: 'abc',
|
||||
schedule: { interval: '10s' },
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
throttle: '30s',
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const systemAction: RuleSystemAction = {
|
||||
actionTypeId: 'test-2',
|
||||
id: 'system_action-id',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
uuid: '123-456',
|
||||
};
|
||||
|
||||
const createResult: Rule<{ bar: boolean }> = {
|
||||
...mockedAlert,
|
||||
enabled: true,
|
||||
muteAll: false,
|
||||
createdBy: '',
|
||||
updatedBy: '',
|
||||
apiKey: '',
|
||||
apiKeyOwner: '',
|
||||
mutedInstanceIds: [],
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
createdAt,
|
||||
updatedAt,
|
||||
id: '123',
|
||||
actions: [
|
||||
{
|
||||
...mockedAlert.actions[0],
|
||||
actionTypeId: 'test',
|
||||
},
|
||||
],
|
||||
executionStatus: {
|
||||
status: 'unknown',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
},
|
||||
revision: 0,
|
||||
};
|
||||
|
||||
it('creates an alert with proper parameters', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
createAlertRoute({
|
||||
router,
|
||||
licenseState,
|
||||
encryptedSavedObjects,
|
||||
usageCounter: mockUsageCounter,
|
||||
docLinks,
|
||||
});
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id?}"`);
|
||||
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.create.mockResolvedValueOnce(createResult);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
body: mockedAlert,
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual({ body: createResult });
|
||||
|
||||
expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled();
|
||||
expect(rulesClient.create).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.create.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data": Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"group": "default",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"alertTypeId": "1",
|
||||
"consumer": "bar",
|
||||
"name": "abc",
|
||||
"notifyWhen": "onActionGroupChange",
|
||||
"params": Object {
|
||||
"bar": true,
|
||||
},
|
||||
"schedule": Object {
|
||||
"interval": "10s",
|
||||
},
|
||||
"tags": Array [
|
||||
"foo",
|
||||
],
|
||||
"throttle": "30s",
|
||||
},
|
||||
"options": Object {
|
||||
"id": undefined,
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: createResult,
|
||||
});
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
createAlertRoute({
|
||||
router,
|
||||
licenseState,
|
||||
encryptedSavedObjects,
|
||||
usageCounter: mockUsageCounter,
|
||||
isServerless: true,
|
||||
docLinks,
|
||||
});
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id?}"`);
|
||||
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('allows providing a custom id when space is undefined', async () => {
|
||||
const expectedResult = {
|
||||
...createResult,
|
||||
id: 'custom-id',
|
||||
};
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
createAlertRoute({
|
||||
router,
|
||||
licenseState,
|
||||
encryptedSavedObjects,
|
||||
usageCounter: mockUsageCounter,
|
||||
docLinks,
|
||||
});
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id?}"`);
|
||||
|
||||
rulesClient.create.mockResolvedValueOnce(expectedResult);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: 'custom-id' },
|
||||
body: mockedAlert,
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual({ body: expectedResult });
|
||||
|
||||
expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.create).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.create.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data": Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"group": "default",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"alertTypeId": "1",
|
||||
"consumer": "bar",
|
||||
"name": "abc",
|
||||
"notifyWhen": "onActionGroupChange",
|
||||
"params": Object {
|
||||
"bar": true,
|
||||
},
|
||||
"schedule": Object {
|
||||
"interval": "10s",
|
||||
},
|
||||
"tags": Array [
|
||||
"foo",
|
||||
],
|
||||
"throttle": "30s",
|
||||
},
|
||||
"options": Object {
|
||||
"id": "custom-id",
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: expectedResult,
|
||||
});
|
||||
});
|
||||
|
||||
it('allows providing a custom id in default space', async () => {
|
||||
const expectedResult = {
|
||||
...createResult,
|
||||
id: 'custom-id',
|
||||
};
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
createAlertRoute({
|
||||
router,
|
||||
licenseState,
|
||||
encryptedSavedObjects,
|
||||
usageCounter: mockUsageCounter,
|
||||
docLinks,
|
||||
});
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id?}"`);
|
||||
|
||||
rulesClient.create.mockResolvedValueOnce(expectedResult);
|
||||
rulesClient.getSpaceId.mockReturnValueOnce('default');
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: 'custom-id' },
|
||||
body: mockedAlert,
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual({ body: expectedResult });
|
||||
|
||||
expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.create).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.create.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data": Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"group": "default",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"alertTypeId": "1",
|
||||
"consumer": "bar",
|
||||
"name": "abc",
|
||||
"notifyWhen": "onActionGroupChange",
|
||||
"params": Object {
|
||||
"bar": true,
|
||||
},
|
||||
"schedule": Object {
|
||||
"interval": "10s",
|
||||
},
|
||||
"tags": Array [
|
||||
"foo",
|
||||
],
|
||||
"throttle": "30s",
|
||||
},
|
||||
"options": Object {
|
||||
"id": "custom-id",
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: expectedResult,
|
||||
});
|
||||
});
|
||||
|
||||
it('allows providing a custom id in non-default space', async () => {
|
||||
const expectedResult = {
|
||||
...createResult,
|
||||
id: 'custom-id',
|
||||
};
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
createAlertRoute({
|
||||
router,
|
||||
licenseState,
|
||||
encryptedSavedObjects,
|
||||
usageCounter: mockUsageCounter,
|
||||
docLinks,
|
||||
});
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id?}"`);
|
||||
|
||||
rulesClient.create.mockResolvedValueOnce(expectedResult);
|
||||
rulesClient.getSpaceId.mockReturnValueOnce('another-space');
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: 'custom-id' },
|
||||
body: mockedAlert,
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual({ body: expectedResult });
|
||||
|
||||
expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(2);
|
||||
expect(rulesClient.create).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.create.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data": Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"group": "default",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"alertTypeId": "1",
|
||||
"consumer": "bar",
|
||||
"name": "abc",
|
||||
"notifyWhen": "onActionGroupChange",
|
||||
"params": Object {
|
||||
"bar": true,
|
||||
},
|
||||
"schedule": Object {
|
||||
"interval": "10s",
|
||||
},
|
||||
"tags": Array [
|
||||
"foo",
|
||||
],
|
||||
"throttle": "30s",
|
||||
},
|
||||
"options": Object {
|
||||
"id": "custom-id",
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: expectedResult,
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures the license allows creating alerts', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
|
||||
createAlertRoute({ router, licenseState, encryptedSavedObjects, docLinks });
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
rulesClient.create.mockResolvedValueOnce(createResult);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, {});
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('ensures the license check prevents creating alerts', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
|
||||
(verifyApiAccess as jest.Mock).mockImplementation(() => {
|
||||
throw new Error('OMG');
|
||||
});
|
||||
|
||||
createAlertRoute({ router, licenseState, encryptedSavedObjects, docLinks });
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
rulesClient.create.mockResolvedValueOnce(createResult);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, {});
|
||||
|
||||
await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('ensures the alert type gets validated for the license', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
|
||||
createAlertRoute({ router, licenseState, encryptedSavedObjects, docLinks });
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
rulesClient.create.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid'));
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, {}, ['ok', 'forbidden']);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } });
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
createAlertRoute({
|
||||
router,
|
||||
licenseState,
|
||||
encryptedSavedObjects,
|
||||
usageCounter: mockUsageCounter,
|
||||
docLinks,
|
||||
});
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
rulesClient.create.mockResolvedValueOnce(createResult);
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, {}, ['ok']);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('create', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('does not return system actions', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
createAlertRoute({
|
||||
router,
|
||||
licenseState,
|
||||
encryptedSavedObjects,
|
||||
usageCounter: mockUsageCounter,
|
||||
docLinks,
|
||||
});
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id?}"`);
|
||||
|
||||
rulesClient.create.mockResolvedValueOnce({ ...createResult, systemActions: [systemAction] });
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
body: mockedAlert,
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual({ body: createResult });
|
||||
|
||||
expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled();
|
||||
expect(rulesClient.create).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.create.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data": Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"group": "default",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"alertTypeId": "1",
|
||||
"consumer": "bar",
|
||||
"name": "abc",
|
||||
"notifyWhen": "onActionGroupChange",
|
||||
"params": Object {
|
||||
"bar": true,
|
||||
},
|
||||
"schedule": Object {
|
||||
"interval": "10s",
|
||||
},
|
||||
"tags": Array [
|
||||
"foo",
|
||||
],
|
||||
"throttle": "30s",
|
||||
},
|
||||
"options": Object {
|
||||
"id": undefined,
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: createResult,
|
||||
});
|
||||
});
|
||||
|
||||
it('should be deprecated', () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
createAlertRoute({
|
||||
router,
|
||||
licenseState,
|
||||
encryptedSavedObjects,
|
||||
usageCounter: mockUsageCounter,
|
||||
docLinks,
|
||||
});
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "POST",
|
||||
"newApiPath": "/api/alerting/rule/{id?}",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,122 +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 { schema } from '@kbn/config-schema';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { validateDurationSchema } from '../../lib';
|
||||
import { handleDisabledApiKeysError } from '../lib/error_handler';
|
||||
import {
|
||||
SanitizedRule,
|
||||
RuleNotifyWhenType,
|
||||
RuleTypeParams,
|
||||
LEGACY_BASE_ALERT_API_PATH,
|
||||
validateNotifyWhenType,
|
||||
} from '../../types';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { RouteOptions } from '..';
|
||||
import { countUsageOfPredefinedIds } from '../lib';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
export const bodySchema = schema.object({
|
||||
name: schema.string(),
|
||||
alertTypeId: schema.string(),
|
||||
enabled: schema.boolean({ defaultValue: true }),
|
||||
consumer: schema.string(),
|
||||
tags: schema.arrayOf(schema.string(), { defaultValue: [] }),
|
||||
throttle: schema.nullable(schema.string({ validate: validateDurationSchema })),
|
||||
params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
|
||||
schedule: schema.object({
|
||||
interval: schema.string({ validate: validateDurationSchema }),
|
||||
}),
|
||||
actions: schema.arrayOf(
|
||||
schema.object({
|
||||
group: schema.string(),
|
||||
id: schema.string(),
|
||||
actionTypeId: schema.maybe(schema.string()),
|
||||
params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
|
||||
}),
|
||||
{ defaultValue: [] }
|
||||
),
|
||||
notifyWhen: schema.nullable(schema.string({ validate: validateNotifyWhenType })),
|
||||
});
|
||||
|
||||
export const createAlertRoute = ({
|
||||
router,
|
||||
licenseState,
|
||||
usageCounter,
|
||||
isServerless,
|
||||
docLinks,
|
||||
}: RouteOptions) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id?}`,
|
||||
validate: {
|
||||
params: schema.maybe(
|
||||
schema.object({
|
||||
id: schema.maybe(schema.string()),
|
||||
})
|
||||
),
|
||||
body: bodySchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Create an alert',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'POST',
|
||||
newApiPath: '/api/alerting/rule/{id?}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
handleDisabledApiKeysError(
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const alert = req.body;
|
||||
const params = req.params;
|
||||
const notifyWhen = alert?.notifyWhen ? (alert.notifyWhen as RuleNotifyWhenType) : null;
|
||||
|
||||
trackLegacyRouteUsage('create', usageCounter);
|
||||
|
||||
countUsageOfPredefinedIds({
|
||||
predefinedId: params?.id,
|
||||
spaceId: rulesClient.getSpaceId(),
|
||||
usageCounter,
|
||||
});
|
||||
|
||||
try {
|
||||
const { systemActions, ...alertRes }: SanitizedRule<RuleTypeParams> =
|
||||
await rulesClient.create<RuleTypeParams>({
|
||||
data: { ...alert, notifyWhen },
|
||||
options: { id: params?.id },
|
||||
});
|
||||
return res.ok({
|
||||
body: alertRes,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof RuleTypeDisabledError) {
|
||||
return e.sendResponse(res);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
|
@ -1,172 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { deleteAlertRoute } from './delete';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('deleteAlertRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
|
||||
it('deletes an alert with proper parameters', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
deleteAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.delete.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.delete.mockResolvedValueOnce({});
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.delete).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.delete.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
deleteAlertRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.delete.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the license allows deleting alerts', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
deleteAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.delete.mock.calls[0];
|
||||
|
||||
rulesClient.delete.mockResolvedValueOnce({});
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: '1' },
|
||||
}
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('ensures the license check prevents deleting alerts', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
(verifyApiAccess as jest.Mock).mockImplementation(() => {
|
||||
throw new Error('OMG');
|
||||
});
|
||||
|
||||
deleteAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.delete.mock.calls[0];
|
||||
|
||||
rulesClient.delete.mockResolvedValueOnce({});
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
id: '1',
|
||||
}
|
||||
);
|
||||
|
||||
await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
deleteAlertRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.delete.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: { id: '1' } }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('delete', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
deleteAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config] = router.delete.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "DELETE",
|
||||
"newApiPath": "/api/alerting/rule/{id}",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,64 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
export const deleteAlertRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.delete(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Delete an alert',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'DELETE',
|
||||
newApiPath: '/api/alerting/rule/{id}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('delete', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { id } = req.params;
|
||||
await rulesClient.delete({ id });
|
||||
return res.noContent();
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,143 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { disableAlertRoute } from './disable';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('disableAlertRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
|
||||
it('disables an alert', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
disableAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_disable"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.disableRule.mockResolvedValueOnce();
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.disableRule).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.disableRule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
disableAlertRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_disable"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the alert type gets validated for the license', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
disableAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
rulesClient.disableRule.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid'));
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
'forbidden',
|
||||
]);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } });
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
disableAlertRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: { id: '1' } }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('disable', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
disableAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "POST",
|
||||
"newApiPath": "/api/alerting/rule/{id}/_disable",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,72 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
export const disableAlertRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_disable`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Disable an alert',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'POST',
|
||||
newApiPath: '/api/alerting/rule/{id}/_disable',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('disable', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { id } = req.params;
|
||||
try {
|
||||
await rulesClient.disableRule({ id });
|
||||
return res.noContent();
|
||||
} catch (e) {
|
||||
if (e instanceof RuleTypeDisabledError) {
|
||||
return e.sendResponse(res);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,143 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { enableAlertRoute } from './enable';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('enableAlertRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
|
||||
it('enables an alert', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
enableAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_enable"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.enableRule.mockResolvedValueOnce();
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.enableRule).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.enableRule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
enableAlertRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_enable"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the alert type gets validated for the license', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
enableAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
rulesClient.enableRule.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid'));
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
'forbidden',
|
||||
]);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } });
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
enableAlertRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: { id: '1' } }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('enable', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
enableAlertRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "POST",
|
||||
"newApiPath": "/api/alerting/rule/{id}/_enable",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,75 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { handleDisabledApiKeysError } from '../lib/error_handler';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
export const enableAlertRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_enable`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Enable an alert',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'POST',
|
||||
newApiPath: '/api/alerting/rule/{id}/_enable',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
handleDisabledApiKeysError(
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('enable', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { id } = req.params;
|
||||
try {
|
||||
await rulesClient.enableRule({ id });
|
||||
return res.noContent();
|
||||
} catch (e) {
|
||||
if (e instanceof RuleTypeDisabledError) {
|
||||
return e.sendResponse(res);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
|
@ -1,439 +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 { omit } from 'lodash';
|
||||
import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { findAlertRoute } from './find';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { trackLegacyTerminology } from '../lib/track_legacy_terminology';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../lib/track_legacy_terminology', () => ({
|
||||
trackLegacyTerminology: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('findAlertRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
|
||||
it('finds alerts with proper parameters', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
findAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_find"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
const findResult = {
|
||||
page: 1,
|
||||
perPage: 1,
|
||||
total: 0,
|
||||
data: [],
|
||||
};
|
||||
rulesClient.find.mockResolvedValueOnce(findResult);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
query: {
|
||||
per_page: 1,
|
||||
page: 1,
|
||||
default_search_operator: 'OR',
|
||||
},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Object {
|
||||
"data": Array [],
|
||||
"page": 1,
|
||||
"perPage": 1,
|
||||
"total": 0,
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
expect(rulesClient.find).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.find.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"excludeFromPublicApi": true,
|
||||
"options": Object {
|
||||
"defaultSearchOperator": "OR",
|
||||
"page": 1,
|
||||
"perPage": 1,
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: findResult,
|
||||
});
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
findAlertRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_find"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the license allows finding alerts', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
findAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
rulesClient.find.mockResolvedValueOnce({
|
||||
page: 1,
|
||||
perPage: 1,
|
||||
total: 0,
|
||||
data: [],
|
||||
});
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
query: {
|
||||
per_page: 1,
|
||||
page: 1,
|
||||
default_search_operator: 'OR',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('ensures the license check prevents finding alerts', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
(verifyApiAccess as jest.Mock).mockImplementation(() => {
|
||||
throw new Error('OMG');
|
||||
});
|
||||
|
||||
findAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{},
|
||||
{
|
||||
query: {
|
||||
per_page: 1,
|
||||
page: 1,
|
||||
default_search_operator: 'OR',
|
||||
},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
findAlertRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
const findResult = {
|
||||
page: 1,
|
||||
perPage: 1,
|
||||
total: 0,
|
||||
data: [],
|
||||
};
|
||||
rulesClient.find.mockResolvedValueOnce(findResult);
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, query: {} }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('find', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should track calls with deprecated param values', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
findAlertRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
const findResult = {
|
||||
page: 1,
|
||||
perPage: 1,
|
||||
total: 0,
|
||||
data: [],
|
||||
};
|
||||
rulesClient.find.mockResolvedValueOnce(findResult);
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {},
|
||||
query: {
|
||||
search_fields: ['alertTypeId:1', 'message:foo'],
|
||||
search: 'alertTypeId:2',
|
||||
sort_field: 'alertTypeId',
|
||||
},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyTerminology).toHaveBeenCalledTimes(1);
|
||||
expect((trackLegacyTerminology as jest.Mock).mock.calls[0][0]).toStrictEqual([
|
||||
'alertTypeId:2',
|
||||
['alertTypeId:1', 'message:foo'],
|
||||
'alertTypeId',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should track calls to deprecated functionality', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
findAlertRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
const findResult = {
|
||||
page: 1,
|
||||
perPage: 1,
|
||||
total: 0,
|
||||
data: [],
|
||||
};
|
||||
rulesClient.find.mockResolvedValueOnce(findResult);
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {},
|
||||
query: {
|
||||
fields: ['foo', 'bar'],
|
||||
},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
await handler(context, req, res);
|
||||
expect(mockUsageCounter.incrementCounter).toHaveBeenCalledWith({
|
||||
counterName: `legacyAlertingFieldsUsage`,
|
||||
counterType: 'alertingFieldsUsage',
|
||||
incrementBy: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not return system actions', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
findAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_find"`);
|
||||
|
||||
const findResult = {
|
||||
page: 1,
|
||||
perPage: 1,
|
||||
total: 0,
|
||||
data: [
|
||||
{
|
||||
id: '3d534c70-582b-11ec-8995-2b1578a3bc5d',
|
||||
notifyWhen: 'onActiveAlert' as const,
|
||||
alertTypeId: '.index-threshold',
|
||||
name: 'stressing index-threshold 37/200',
|
||||
consumer: 'alerts',
|
||||
tags: [],
|
||||
enabled: true,
|
||||
throttle: null,
|
||||
apiKey: null,
|
||||
apiKeyOwner: '2889684073',
|
||||
createdBy: 'elastic',
|
||||
updatedBy: '2889684073',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
schedule: {
|
||||
interval: '1s',
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
actionTypeId: '.server-log',
|
||||
params: {
|
||||
message: 'alert 37: {{context.message}}',
|
||||
},
|
||||
group: 'threshold met',
|
||||
id: '3619a0d0-582b-11ec-8995-2b1578a3bc5d',
|
||||
uuid: '123-456',
|
||||
},
|
||||
],
|
||||
systemActions: [
|
||||
{ actionTypeId: '.test', id: 'system_action-id', params: {}, uuid: '789' },
|
||||
],
|
||||
params: { x: 42 },
|
||||
updatedAt: '2024-03-21T13:15:00.498Z',
|
||||
createdAt: '2024-03-21T13:15:00.498Z',
|
||||
scheduledTaskId: '52125fb0-5895-11ec-ae69-bb65d1a71b72',
|
||||
executionStatus: {
|
||||
status: 'ok' as const,
|
||||
lastExecutionDate: '2024-03-21T13:15:00.498Z',
|
||||
lastDuration: 1194,
|
||||
},
|
||||
revision: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// @ts-expect-error: TS complains about dates being string and not a Date object
|
||||
rulesClient.find.mockResolvedValueOnce(findResult);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
query: {
|
||||
per_page: 1,
|
||||
page: 1,
|
||||
default_search_operator: 'OR',
|
||||
},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Object {
|
||||
"data": Array [
|
||||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"actionTypeId": ".server-log",
|
||||
"group": "threshold met",
|
||||
"id": "3619a0d0-582b-11ec-8995-2b1578a3bc5d",
|
||||
"params": Object {
|
||||
"message": "alert 37: {{context.message}}",
|
||||
},
|
||||
"uuid": "123-456",
|
||||
},
|
||||
],
|
||||
"alertTypeId": ".index-threshold",
|
||||
"apiKey": null,
|
||||
"apiKeyOwner": "2889684073",
|
||||
"consumer": "alerts",
|
||||
"createdAt": "2024-03-21T13:15:00.498Z",
|
||||
"createdBy": "elastic",
|
||||
"enabled": true,
|
||||
"executionStatus": Object {
|
||||
"lastDuration": 1194,
|
||||
"lastExecutionDate": "2024-03-21T13:15:00.498Z",
|
||||
"status": "ok",
|
||||
},
|
||||
"id": "3d534c70-582b-11ec-8995-2b1578a3bc5d",
|
||||
"muteAll": false,
|
||||
"mutedInstanceIds": Array [],
|
||||
"name": "stressing index-threshold 37/200",
|
||||
"notifyWhen": "onActiveAlert",
|
||||
"params": Object {
|
||||
"x": 42,
|
||||
},
|
||||
"revision": 0,
|
||||
"schedule": Object {
|
||||
"interval": "1s",
|
||||
},
|
||||
"scheduledTaskId": "52125fb0-5895-11ec-ae69-bb65d1a71b72",
|
||||
"tags": Array [],
|
||||
"throttle": null,
|
||||
"updatedAt": "2024-03-21T13:15:00.498Z",
|
||||
"updatedBy": "2889684073",
|
||||
},
|
||||
],
|
||||
"page": 1,
|
||||
"perPage": 1,
|
||||
"total": 0,
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
expect(rulesClient.find).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.find.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"excludeFromPublicApi": true,
|
||||
"options": Object {
|
||||
"defaultSearchOperator": "OR",
|
||||
"page": 1,
|
||||
"perPage": 1,
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: omit(findResult, 'data[0].systemActions'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
findAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "GET",
|
||||
"newApiPath": "/api/alerting/rules/_find",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,150 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { KueryNode } from '@kbn/es-query';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { renameKeys } from '../lib/rename_keys';
|
||||
import { IndexType } from '../../rules_client';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { trackLegacyTerminology } from '../lib/track_legacy_terminology';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
export interface FindOptions extends IndexType {
|
||||
perPage?: number;
|
||||
page?: number;
|
||||
search?: string;
|
||||
defaultSearchOperator?: 'AND' | 'OR';
|
||||
searchFields?: string[];
|
||||
sortField?: string;
|
||||
sortOrder?: estypes.SortOrder;
|
||||
hasReference?: {
|
||||
type: string;
|
||||
id: string;
|
||||
};
|
||||
fields?: string[];
|
||||
filter?: string | KueryNode;
|
||||
filterConsumers?: string[];
|
||||
}
|
||||
|
||||
// config definition
|
||||
const querySchema = schema.object({
|
||||
per_page: schema.number({ defaultValue: 10, min: 0 }),
|
||||
page: schema.number({ defaultValue: 1, min: 1 }),
|
||||
search: schema.maybe(schema.string()),
|
||||
default_search_operator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], {
|
||||
defaultValue: 'OR',
|
||||
}),
|
||||
search_fields: schema.maybe(schema.oneOf([schema.arrayOf(schema.string()), schema.string()])),
|
||||
sort_field: schema.maybe(schema.string()),
|
||||
sort_order: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])),
|
||||
has_reference: schema.maybe(
|
||||
// use nullable as maybe is currently broken
|
||||
// in config-schema
|
||||
schema.nullable(
|
||||
schema.object({
|
||||
type: schema.string(),
|
||||
id: schema.string(),
|
||||
})
|
||||
)
|
||||
),
|
||||
fields: schema.maybe(schema.arrayOf(schema.string())),
|
||||
filter: schema.maybe(schema.string()),
|
||||
});
|
||||
|
||||
export const findAlertRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/_find`,
|
||||
validate: {
|
||||
query: querySchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Find alerts',
|
||||
tags: ['oas-tag:alerting'],
|
||||
description:
|
||||
'Gets a paginated set of alerts. Alert `params` are stored as a flattened field type and analyzed as keywords. As alerts change in Kibana, the results on each page of the response also change. Use the find API for traditional paginated results, but avoid using it to export large amounts of data.',
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'GET',
|
||||
newApiPath: '/api/alerting/rules/_find',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('find', usageCounter);
|
||||
trackLegacyTerminology(
|
||||
[req.query.search, req.query.search_fields, req.query.sort_field].filter(
|
||||
Boolean
|
||||
) as string[],
|
||||
usageCounter
|
||||
);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
|
||||
const query = req.query;
|
||||
const renameMap = {
|
||||
default_search_operator: 'defaultSearchOperator',
|
||||
fields: 'fields',
|
||||
has_reference: 'hasReference',
|
||||
page: 'page',
|
||||
per_page: 'perPage',
|
||||
search: 'search',
|
||||
sort_field: 'sortField',
|
||||
sort_order: 'sortOrder',
|
||||
filter: 'filter',
|
||||
};
|
||||
|
||||
const options = renameKeys<FindOptions, Record<string, unknown>>(renameMap, query);
|
||||
|
||||
if (query.search_fields) {
|
||||
options.searchFields = Array.isArray(query.search_fields)
|
||||
? query.search_fields
|
||||
: [query.search_fields];
|
||||
}
|
||||
|
||||
if (query.fields) {
|
||||
usageCounter?.incrementCounter({
|
||||
counterName: `legacyAlertingFieldsUsage`,
|
||||
counterType: 'alertingFieldsUsage',
|
||||
incrementBy: 1,
|
||||
});
|
||||
}
|
||||
|
||||
const findResult = await rulesClient.find({ options, excludeFromPublicApi: true });
|
||||
return res.ok({
|
||||
body: {
|
||||
...findResult,
|
||||
data: findResult.data.map(({ systemActions, ...rule }) => rule),
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,243 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { getAlertRoute } from './get';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { Rule, RuleSystemAction } from '../../../common';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('getAlertRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
const mockedAlert: Rule<{
|
||||
bar: true;
|
||||
}> = {
|
||||
id: '1',
|
||||
alertTypeId: '1',
|
||||
schedule: { interval: '10s' },
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
consumer: 'bar',
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
enabled: true,
|
||||
muteAll: false,
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
createdBy: '',
|
||||
updatedBy: '',
|
||||
apiKey: '',
|
||||
apiKeyOwner: '',
|
||||
throttle: '30s',
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'unknown',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
},
|
||||
revision: 0,
|
||||
};
|
||||
|
||||
const systemAction: RuleSystemAction = {
|
||||
actionTypeId: 'test-2',
|
||||
id: 'system_action-id',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
uuid: '123-456',
|
||||
};
|
||||
|
||||
it('gets an alert with proper parameters', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertRoute(router, licenseState, docLinks);
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.get.mockResolvedValueOnce(mockedAlert);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: '1' },
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(rulesClient.get).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.get.mock.calls[0][0].id).toEqual('1');
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: mockedAlert,
|
||||
});
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertRoute(router, licenseState, docLinks, undefined, true);
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the license allows getting alerts', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
rulesClient.get.mockResolvedValueOnce(mockedAlert);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: '1' },
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('ensures the license check prevents getting alerts', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
(verifyApiAccess as jest.Mock).mockImplementation(() => {
|
||||
throw new Error('OMG');
|
||||
});
|
||||
|
||||
getAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
rulesClient.get.mockResolvedValueOnce(mockedAlert);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: '1' },
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
getAlertRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
rulesClient.get.mockResolvedValueOnce(mockedAlert);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: { id: '1' } }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('get', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('does not return system actions', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertRoute(router, licenseState, docLinks);
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`);
|
||||
|
||||
rulesClient.get.mockResolvedValueOnce({ ...mockedAlert, systemActions: [systemAction] });
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: '1' },
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(rulesClient.get).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.get.mock.calls[0][0].id).toEqual('1');
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: mockedAlert,
|
||||
});
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertRoute(router, licenseState, docLinks);
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "GET",
|
||||
"newApiPath": "/api/alerting/rule/{id}",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,66 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
export const getAlertRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Get an alert',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'GET',
|
||||
newApiPath: '/api/alerting/rule/{id}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('get', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { id } = req.params;
|
||||
const { systemActions, ...rule } = await rulesClient.get({ id, excludeFromPublicApi: true });
|
||||
return res.ok({
|
||||
body: rule,
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,177 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { getAlertInstanceSummaryRoute } from './get_alert_instance_summary';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { AlertSummary } from '../../types';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('getAlertInstanceSummaryRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
const dateString = new Date().toISOString();
|
||||
const mockedAlertInstanceSummary: AlertSummary = {
|
||||
id: '',
|
||||
name: '',
|
||||
tags: [],
|
||||
ruleTypeId: '',
|
||||
consumer: '',
|
||||
muteAll: false,
|
||||
throttle: null,
|
||||
enabled: false,
|
||||
statusStartDate: dateString,
|
||||
statusEndDate: dateString,
|
||||
status: 'OK',
|
||||
errorMessages: [],
|
||||
alerts: {},
|
||||
executionDuration: {
|
||||
average: 0,
|
||||
valuesWithTimestamp: {},
|
||||
},
|
||||
revision: 0,
|
||||
};
|
||||
|
||||
it('gets alert instance summary', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertInstanceSummaryRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_instance_summary"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.getAlertSummary.mockResolvedValueOnce(mockedAlertInstanceSummary);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
query: {},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(rulesClient.getAlertSummary).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.getAlertSummary.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"dateStart": undefined,
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertInstanceSummaryRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_instance_summary"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('returns NOT-FOUND when alert is not found', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertInstanceSummaryRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
rulesClient.getAlertSummary = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
SavedObjectsErrorHelpers.createGenericNotFoundError(RULE_SAVED_OBJECT_TYPE, '1')
|
||||
);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
query: {},
|
||||
},
|
||||
['notFound']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
getAlertInstanceSummaryRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
rulesClient.getAlertSummary.mockResolvedValueOnce(mockedAlertInstanceSummary);
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{ params: { id: '1' }, query: {} },
|
||||
['ok']
|
||||
);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('instanceSummary', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertInstanceSummaryRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"type": "remove",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,75 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { AlertSummary, LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
const querySchema = schema.object({
|
||||
dateStart: schema.maybe(schema.string()),
|
||||
});
|
||||
|
||||
const rewriteBodyRes = ({ ruleTypeId, alerts, ...rest }: AlertSummary) => ({
|
||||
...rest,
|
||||
alertTypeId: ruleTypeId,
|
||||
instances: alerts,
|
||||
});
|
||||
|
||||
export const getAlertInstanceSummaryRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_instance_summary`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
query: querySchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Get an alert summary',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'remove',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('instanceSummary', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { id } = req.params;
|
||||
const { dateStart } = req.query;
|
||||
const summary = await rulesClient.getAlertSummary({ id, dateStart });
|
||||
|
||||
return res.ok({ body: rewriteBodyRes(summary) });
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,212 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { getAlertStateRoute } from './get_alert_state';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('getAlertStateRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
const mockedAlertState = {
|
||||
alertTypeState: {
|
||||
some: 'value',
|
||||
},
|
||||
alertInstances: {
|
||||
first_instance: {
|
||||
state: {},
|
||||
meta: {
|
||||
lastScheduledActions: {
|
||||
group: 'first_group',
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
second_instance: {},
|
||||
},
|
||||
};
|
||||
|
||||
it('gets alert state', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertStateRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/state"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.getAlertState.mockResolvedValueOnce(mockedAlertState);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(rulesClient.getAlertState).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.getAlertState.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertStateRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/state"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('returns NO-CONTENT when alert exists but has no task state yet', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertStateRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/state"`);
|
||||
|
||||
rulesClient.getAlertState.mockResolvedValueOnce(undefined);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.getAlertState).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.getAlertState.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns NOT-FOUND when alert is not found', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertStateRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/state"`);
|
||||
|
||||
rulesClient.getAlertState = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
SavedObjectsErrorHelpers.createGenericNotFoundError(RULE_SAVED_OBJECT_TYPE, '1')
|
||||
);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
['notFound']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.getAlertState).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.getAlertState.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
getAlertStateRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: { id: '1' } }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('state', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getAlertStateRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"type": "remove",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,62 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
export const getAlertStateRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/state`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Get the state of an alert',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'remove',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('state', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { id } = req.params;
|
||||
const state = await rulesClient.getAlertState({ id });
|
||||
return state ? res.ok({ body: state }) : res.noContent();
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,473 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { HealthStatus } from '@kbn/alerting-types';
|
||||
import { healthRoute } from './health';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { RecoveredActionGroup } from '../../types';
|
||||
import { alertsMock } from '../../mocks';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { RegistryAlertTypeWithAuth } from '../../authorization';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
const alerting = alertsMock.createStart();
|
||||
|
||||
const currentDate = new Date().toISOString();
|
||||
|
||||
const ruleTypes = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: true,
|
||||
ruleTaskTimeout: '10m',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
authorizedConsumers: {},
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
category: 'test',
|
||||
producer: 'test',
|
||||
enabledInLicense: true,
|
||||
defaultScheduleInterval: '10m',
|
||||
hasAlertsMappings: false,
|
||||
hasFieldsForAAD: false,
|
||||
validLegacyConsumers: [],
|
||||
} as RegistryAlertTypeWithAuth,
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
alerting.getFrameworkHealth.mockResolvedValue({
|
||||
decryptionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
executionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
readHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('healthRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
|
||||
it('registers the route', async () => {
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(ruleTypes);
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects, docLinks);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_health"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(ruleTypes);
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_health"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('throws error when user does not have any access to any rule types', async () => {
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce([]);
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: false });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects, docLinks);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{
|
||||
rulesClient,
|
||||
getFrameworkHealth: alerting.getFrameworkHealth,
|
||||
areApiKeysEnabled: () => Promise.resolve(true),
|
||||
},
|
||||
{},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({
|
||||
body: { message: `Unauthorized to access alerting framework health` },
|
||||
});
|
||||
});
|
||||
|
||||
it('evaluates whether Encrypted Saved Objects is missing encryption key', async () => {
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(ruleTypes);
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: false });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects, docLinks);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{
|
||||
rulesClient,
|
||||
getFrameworkHealth: alerting.getFrameworkHealth,
|
||||
areApiKeysEnabled: () => Promise.resolve(true),
|
||||
},
|
||||
{},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toStrictEqual({
|
||||
body: {
|
||||
alertingFrameworkHeath: {
|
||||
// Legacy: pre-v8.0 typo
|
||||
_deprecated: 'This state property has a typo, use "alertingFrameworkHealth" instead.',
|
||||
decryptionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
executionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
readHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
},
|
||||
alertingFrameworkHealth: {
|
||||
decryptionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
executionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
readHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
},
|
||||
hasPermanentEncryptionKey: false,
|
||||
isSufficientlySecure: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('when ES security status cannot be determined from license state, isSufficientlySecure should return false', async () => {
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(ruleTypes);
|
||||
const router = httpServiceMock.createRouter();
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
licenseState.getIsSecurityEnabled.mockReturnValueOnce(null);
|
||||
|
||||
healthRoute(router, licenseState, encryptedSavedObjects, docLinks);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{
|
||||
rulesClient,
|
||||
getFrameworkHealth: alerting.getFrameworkHealth,
|
||||
areApiKeysEnabled: () => Promise.resolve(true),
|
||||
},
|
||||
{},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toStrictEqual({
|
||||
body: {
|
||||
alertingFrameworkHeath: {
|
||||
// Legacy: pre-v8.0 typo
|
||||
_deprecated: 'This state property has a typo, use "alertingFrameworkHealth" instead.',
|
||||
decryptionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
executionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
readHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
},
|
||||
alertingFrameworkHealth: {
|
||||
decryptionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
executionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
readHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
},
|
||||
hasPermanentEncryptionKey: true,
|
||||
isSufficientlySecure: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('when ES security is disabled, isSufficientlySecure should return true', async () => {
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(ruleTypes);
|
||||
const router = httpServiceMock.createRouter();
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
licenseState.getIsSecurityEnabled.mockReturnValueOnce(false);
|
||||
|
||||
healthRoute(router, licenseState, encryptedSavedObjects, docLinks);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{
|
||||
rulesClient,
|
||||
getFrameworkHealth: alerting.getFrameworkHealth,
|
||||
areApiKeysEnabled: () => Promise.resolve(false),
|
||||
},
|
||||
{},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toStrictEqual({
|
||||
body: {
|
||||
alertingFrameworkHeath: {
|
||||
// Legacy: pre-v8.0 typo
|
||||
_deprecated: 'This state property has a typo, use "alertingFrameworkHealth" instead.',
|
||||
decryptionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
executionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
readHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
},
|
||||
alertingFrameworkHealth: {
|
||||
decryptionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
executionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
readHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
},
|
||||
hasPermanentEncryptionKey: true,
|
||||
isSufficientlySecure: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('when ES security is enabled but user cannot generate api keys, isSufficientlySecure should return false', async () => {
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(ruleTypes);
|
||||
const router = httpServiceMock.createRouter();
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
licenseState.getIsSecurityEnabled.mockReturnValueOnce(true);
|
||||
|
||||
healthRoute(router, licenseState, encryptedSavedObjects, docLinks);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{
|
||||
rulesClient,
|
||||
getFrameworkHealth: alerting.getFrameworkHealth,
|
||||
areApiKeysEnabled: () => Promise.resolve(false),
|
||||
},
|
||||
{},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toStrictEqual({
|
||||
body: {
|
||||
alertingFrameworkHeath: {
|
||||
// Legacy: pre-v8.0 typo
|
||||
_deprecated: 'This state property has a typo, use "alertingFrameworkHealth" instead.',
|
||||
decryptionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
executionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
readHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
},
|
||||
alertingFrameworkHealth: {
|
||||
decryptionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
executionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
readHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
},
|
||||
hasPermanentEncryptionKey: true,
|
||||
isSufficientlySecure: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('when ES security is enabled and user can generate api keys, isSufficientlySecure should return true', async () => {
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(ruleTypes);
|
||||
const router = httpServiceMock.createRouter();
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
licenseState.getIsSecurityEnabled.mockReturnValueOnce(true);
|
||||
|
||||
healthRoute(router, licenseState, encryptedSavedObjects, docLinks);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{
|
||||
rulesClient,
|
||||
getFrameworkHealth: alerting.getFrameworkHealth,
|
||||
areApiKeysEnabled: () => Promise.resolve(true),
|
||||
},
|
||||
{},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toStrictEqual({
|
||||
body: {
|
||||
alertingFrameworkHeath: {
|
||||
// Legacy: pre-v8.0 typo
|
||||
_deprecated: 'This state property has a typo, use "alertingFrameworkHealth" instead.',
|
||||
decryptionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
executionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
readHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
},
|
||||
alertingFrameworkHealth: {
|
||||
decryptionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
executionHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
readHealth: {
|
||||
status: HealthStatus.OK,
|
||||
timestamp: currentDate,
|
||||
},
|
||||
},
|
||||
hasPermanentEncryptionKey: true,
|
||||
isSufficientlySecure: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(ruleTypes);
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
healthRoute(router, licenseState, encryptedSavedObjects, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: { id: '1' } }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('health', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(ruleTypes);
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects, docLinks);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "GET",
|
||||
"newApiPath": "/api/alerting/rule/_health",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,93 +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 { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { AlertingFrameworkHealth } from '../../types';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { getSecurityHealth } from '../../lib/get_security_health';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
export function healthRoute(
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) {
|
||||
router.get(
|
||||
{
|
||||
path: '/api/alerts/_health',
|
||||
validate: false,
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Get the alerting framework health',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'GET',
|
||||
newApiPath: '/api/alerting/rule/_health',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('health', usageCounter);
|
||||
try {
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
// Verify that user has access to at least one rule type
|
||||
const ruleTypes = Array.from(await rulesClient.listRuleTypes());
|
||||
if (ruleTypes.length > 0) {
|
||||
const alertingFrameworkHealth = await alertingContext.getFrameworkHealth();
|
||||
|
||||
const securityHealth = await getSecurityHealth(
|
||||
async () => (licenseState ? licenseState.getIsSecurityEnabled() : null),
|
||||
async () => encryptedSavedObjects.canEncrypt,
|
||||
alertingContext.areApiKeysEnabled
|
||||
);
|
||||
|
||||
const frameworkHealth: AlertingFrameworkHealth = {
|
||||
...securityHealth,
|
||||
alertingFrameworkHealth,
|
||||
};
|
||||
|
||||
return res.ok({
|
||||
body: {
|
||||
...frameworkHealth,
|
||||
alertingFrameworkHeath: {
|
||||
// Legacy: pre-v8.0 typo
|
||||
...alertingFrameworkHealth,
|
||||
_deprecated:
|
||||
'This state property has a typo, use "alertingFrameworkHealth" instead.',
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return res.forbidden({
|
||||
body: { message: `Unauthorized to access alerting framework health` },
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
return res.badRequest({ body: error });
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
|
@ -1,46 +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 { createAlertRoute } from './create';
|
||||
import { deleteAlertRoute } from './delete';
|
||||
import { findAlertRoute } from './find';
|
||||
import { getAlertRoute } from './get';
|
||||
import { getAlertStateRoute } from './get_alert_state';
|
||||
import { getAlertInstanceSummaryRoute } from './get_alert_instance_summary';
|
||||
import { listAlertTypesRoute } from './list_alert_types';
|
||||
import { updateAlertRoute } from './update';
|
||||
import { enableAlertRoute } from './enable';
|
||||
import { disableAlertRoute } from './disable';
|
||||
import { updateApiKeyRoute } from './update_api_key';
|
||||
import { muteAlertInstanceRoute } from './mute_instance';
|
||||
import { unmuteAlertInstanceRoute } from './unmute_instance';
|
||||
import { muteAllAlertRoute } from './mute_all';
|
||||
import { unmuteAllAlertRoute } from './unmute_all';
|
||||
import { healthRoute } from './health';
|
||||
import { RouteOptions } from '..';
|
||||
|
||||
export function defineLegacyRoutes(opts: RouteOptions) {
|
||||
const { router, licenseState, encryptedSavedObjects, usageCounter, isServerless, docLinks } =
|
||||
opts;
|
||||
|
||||
createAlertRoute(opts);
|
||||
deleteAlertRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
findAlertRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
getAlertRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
getAlertStateRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
getAlertInstanceSummaryRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
listAlertTypesRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
updateAlertRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
enableAlertRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
disableAlertRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
updateApiKeyRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
muteAllAlertRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
unmuteAllAlertRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
muteAlertInstanceRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
unmuteAlertInstanceRoute(router, licenseState, docLinks, usageCounter, isServerless);
|
||||
healthRoute(router, licenseState, encryptedSavedObjects, docLinks, usageCounter, isServerless);
|
||||
}
|
|
@ -1,287 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { listAlertTypesRoute } from './list_alert_types';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { RecoveredActionGroup } from '../../../common';
|
||||
import { RegistryAlertTypeWithAuth } from '../../authorization';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('listAlertTypesRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
it('lists alert types with proper parameters', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
listAlertTypesRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/list_alert_types"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
const listTypes: RegistryAlertTypeWithAuth[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: true,
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
authorizedConsumers: {},
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
category: 'test',
|
||||
producer: 'test',
|
||||
enabledInLicense: true,
|
||||
hasAlertsMappings: false,
|
||||
hasFieldsForAAD: false,
|
||||
validLegacyConsumers: [],
|
||||
},
|
||||
];
|
||||
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(listTypes);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, {}, ['ok']);
|
||||
|
||||
expect(await handler(context, req, res)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Array [
|
||||
Object {
|
||||
"actionGroups": Array [
|
||||
Object {
|
||||
"id": "default",
|
||||
"name": "Default",
|
||||
},
|
||||
],
|
||||
"actionVariables": Object {
|
||||
"context": Array [],
|
||||
"state": Array [],
|
||||
},
|
||||
"authorizedConsumers": Object {},
|
||||
"category": "test",
|
||||
"defaultActionGroupId": "default",
|
||||
"enabledInLicense": true,
|
||||
"hasAlertsMappings": false,
|
||||
"hasFieldsForAAD": false,
|
||||
"id": "1",
|
||||
"isExportable": true,
|
||||
"minimumLicenseRequired": "basic",
|
||||
"name": "name",
|
||||
"producer": "test",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
"validLegacyConsumers": Array [],
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
|
||||
expect(rulesClient.listRuleTypes).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: listTypes,
|
||||
});
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
listAlertTypesRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/list_alert_types"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the license allows listing alert types', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
listAlertTypesRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/list_alert_types"`);
|
||||
|
||||
const listTypes = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: true,
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
authorizedConsumers: {},
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
category: 'test',
|
||||
producer: 'alerts',
|
||||
enabledInLicense: true,
|
||||
hasAlertsMappings: false,
|
||||
hasFieldsForAAD: false,
|
||||
validLegacyConsumers: [],
|
||||
} as RegistryAlertTypeWithAuth,
|
||||
];
|
||||
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(listTypes);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: '1' },
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('ensures the license check prevents listing alert types', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
(verifyApiAccess as jest.Mock).mockImplementation(() => {
|
||||
throw new Error('OMG');
|
||||
});
|
||||
|
||||
listAlertTypesRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/list_alert_types"`);
|
||||
|
||||
const listTypes = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: true,
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
authorizedConsumers: {},
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
category: 'test',
|
||||
producer: 'alerts',
|
||||
enabledInLicense: true,
|
||||
hasAlertsMappings: false,
|
||||
hasFieldsForAAD: false,
|
||||
validLegacyConsumers: [],
|
||||
} as RegistryAlertTypeWithAuth,
|
||||
];
|
||||
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(listTypes);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: '1' },
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce([]);
|
||||
|
||||
listAlertTypesRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{ params: { id: '1' }, body: {} },
|
||||
['ok']
|
||||
);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('listAlertTypes', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
listAlertTypesRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "GET",
|
||||
"newApiPath": "/api/alerting/rule_types",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,57 +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 { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
export const listAlertTypesRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/list_alert_types`,
|
||||
validate: {},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Get the alert types',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'GET',
|
||||
newApiPath: '/api/alerting/rule_types',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('listAlertTypes', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
|
||||
return res.ok({
|
||||
body: Array.from(await rulesClient.listRuleTypes()),
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,143 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { muteAllAlertRoute } from './mute_all';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('muteAllAlertRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
|
||||
it('mute an alert', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
muteAllAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_mute_all"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.muteAll.mockResolvedValueOnce();
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.muteAll).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.muteAll.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
muteAllAlertRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_mute_all"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the alert type gets validated for the license', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
muteAllAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
rulesClient.muteAll.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid'));
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
'forbidden',
|
||||
]);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } });
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
muteAllAlertRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('muteAll', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
muteAllAlertRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "POST",
|
||||
"newApiPath": "/api/alerting/rule/{id}/_mute_all",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,72 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
export const muteAllAlertRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_mute_all`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Mute all alert instances',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'POST',
|
||||
newApiPath: '/api/alerting/rule/{id}/_mute_all',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('muteAll', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { id } = req.params;
|
||||
try {
|
||||
await rulesClient.muteAll({ id });
|
||||
return res.noContent();
|
||||
} catch (e) {
|
||||
if (e instanceof RuleTypeDisabledError) {
|
||||
return e.sendResponse(res);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,151 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { muteAlertInstanceRoute } from './mute_instance';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('muteAlertInstanceRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
|
||||
it('mutes an alert instance', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
muteAlertInstanceRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(
|
||||
`"/api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute"`
|
||||
);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.muteInstance.mockResolvedValueOnce();
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
alert_id: '1',
|
||||
alert_instance_id: '2',
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.muteInstance).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.muteInstance.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"alertId": "1",
|
||||
"alertInstanceId": "2",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
muteAlertInstanceRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(
|
||||
`"/api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute"`
|
||||
);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the alert type gets validated for the license', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
muteAlertInstanceRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
rulesClient.muteInstance.mockRejectedValue(
|
||||
new RuleTypeDisabledError('Fail', 'license_invalid')
|
||||
);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
'forbidden',
|
||||
]);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } });
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
muteAlertInstanceRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('muteInstance', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
muteAlertInstanceRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "POST",
|
||||
"newApiPath": "/api/alerting/rule/{rule_id}/alert/{alert_id}/_mute",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,83 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { renameKeys } from '../lib/rename_keys';
|
||||
import { MuteOptions } from '../../rules_client';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
alert_id: schema.string(),
|
||||
alert_instance_id: schema.string(),
|
||||
});
|
||||
|
||||
export const muteAlertInstanceRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Mute an alert',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'POST',
|
||||
newApiPath: '/api/alerting/rule/{rule_id}/alert/{alert_id}/_mute',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
|
||||
trackLegacyRouteUsage('muteInstance', usageCounter);
|
||||
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
|
||||
const renameMap = {
|
||||
alert_id: 'alertId',
|
||||
alert_instance_id: 'alertInstanceId',
|
||||
};
|
||||
|
||||
const renamedQuery = renameKeys<MuteOptions, Record<string, unknown>>(renameMap, req.params);
|
||||
try {
|
||||
await rulesClient.muteInstance(renamedQuery);
|
||||
return res.noContent();
|
||||
} catch (e) {
|
||||
if (e instanceof RuleTypeDisabledError) {
|
||||
return e.sendResponse(res);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,143 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { unmuteAllAlertRoute } from './unmute_all';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('unmuteAllAlertRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
|
||||
it('unmutes an alert', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
unmuteAllAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_unmute_all"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.unmuteAll.mockResolvedValueOnce();
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.unmuteAll).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.unmuteAll.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
unmuteAllAlertRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_unmute_all"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the alert type gets validated for the license', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
unmuteAllAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
rulesClient.unmuteAll.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid'));
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
'forbidden',
|
||||
]);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } });
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
unmuteAllAlertRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('unmuteAll', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
unmuteAllAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "POST",
|
||||
"newApiPath": "/api/alerting/rule/{id}/_unmute_all",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,72 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
export const unmuteAllAlertRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_unmute_all`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Unmute all alert instances',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'POST',
|
||||
newApiPath: '/api/alerting/rule/{id}/_unmute_all',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('unmuteAll', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { id } = req.params;
|
||||
try {
|
||||
await rulesClient.unmuteAll({ id });
|
||||
return res.noContent();
|
||||
} catch (e) {
|
||||
if (e instanceof RuleTypeDisabledError) {
|
||||
return e.sendResponse(res);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,151 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { unmuteAlertInstanceRoute } from './unmute_instance';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('unmuteAlertInstanceRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
|
||||
it('unmutes an alert instance', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
unmuteAlertInstanceRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(
|
||||
`"/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute"`
|
||||
);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.unmuteInstance.mockResolvedValueOnce();
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
alertId: '1',
|
||||
alertInstanceId: '2',
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.unmuteInstance).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.unmuteInstance.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"alertId": "1",
|
||||
"alertInstanceId": "2",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
unmuteAlertInstanceRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(
|
||||
`"/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute"`
|
||||
);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the alert type gets validated for the license', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
unmuteAlertInstanceRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
rulesClient.unmuteInstance.mockRejectedValue(
|
||||
new RuleTypeDisabledError('Fail', 'license_invalid')
|
||||
);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
'forbidden',
|
||||
]);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } });
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
unmuteAlertInstanceRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('unmuteInstance', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
unmuteAlertInstanceRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "POST",
|
||||
"newApiPath": "/api/alerting/rule/{rule_id}/alert/{alert_id}/_unmute",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,73 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
alertId: schema.string(),
|
||||
alertInstanceId: schema.string(),
|
||||
});
|
||||
|
||||
export const unmuteAlertInstanceRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Unmute an alert',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'POST',
|
||||
newApiPath: '/api/alerting/rule/{rule_id}/alert/{alert_id}/_unmute',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('unmuteInstance', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { alertId, alertInstanceId } = req.params;
|
||||
try {
|
||||
await rulesClient.unmuteInstance({ alertId, alertInstanceId });
|
||||
return res.noContent();
|
||||
} catch (e) {
|
||||
if (e instanceof RuleTypeDisabledError) {
|
||||
return e.sendResponse(res);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
|
@ -1,390 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { updateAlertRoute } from './update';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { RuleNotifyWhen, SanitizedRule, RuleSystemAction } from '../../../common';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('updateAlertRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
const mockedResponse = {
|
||||
id: '1',
|
||||
alertTypeId: '1',
|
||||
tags: ['foo'],
|
||||
schedule: { interval: '12s' },
|
||||
params: {
|
||||
otherField: false,
|
||||
},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
baz: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
notifyWhen: RuleNotifyWhen.CHANGE,
|
||||
};
|
||||
|
||||
const systemAction: RuleSystemAction = {
|
||||
actionTypeId: 'test-2',
|
||||
id: 'system_action-id',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
uuid: '123-456',
|
||||
};
|
||||
|
||||
it('updates an alert with proper parameters', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
updateAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.put.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.update.mockResolvedValueOnce(mockedResponse as unknown as SanitizedRule);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
body: {
|
||||
throttle: null,
|
||||
name: 'abc',
|
||||
tags: ['bar'],
|
||||
schedule: { interval: '12s' },
|
||||
params: {
|
||||
otherField: false,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
params: {
|
||||
baz: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual({ body: mockedResponse });
|
||||
|
||||
expect(rulesClient.update).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.update.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data": Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"group": "default",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
"baz": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"name": "abc",
|
||||
"notifyWhen": "onActionGroupChange",
|
||||
"params": Object {
|
||||
"otherField": false,
|
||||
},
|
||||
"schedule": Object {
|
||||
"interval": "12s",
|
||||
},
|
||||
"tags": Array [
|
||||
"bar",
|
||||
],
|
||||
"throttle": null,
|
||||
},
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
updateAlertRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.put.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the license allows updating alerts', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
updateAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.put.mock.calls[0];
|
||||
|
||||
rulesClient.update.mockResolvedValueOnce(mockedResponse as unknown as SanitizedRule);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
body: {
|
||||
throttle: null,
|
||||
name: 'abc',
|
||||
tags: ['bar'],
|
||||
schedule: { interval: '12s' },
|
||||
params: {
|
||||
otherField: false,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
params: {
|
||||
baz: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('ensures the license check prevents updating alerts', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
(verifyApiAccess as jest.Mock).mockImplementation(() => {
|
||||
throw new Error('OMG');
|
||||
});
|
||||
|
||||
updateAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.put.mock.calls[0];
|
||||
|
||||
rulesClient.update.mockResolvedValueOnce(mockedResponse as unknown as SanitizedRule);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
body: {
|
||||
throttle: null,
|
||||
name: 'abc',
|
||||
tags: ['bar'],
|
||||
schedule: { interval: '12s' },
|
||||
params: {
|
||||
otherField: false,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
params: {
|
||||
baz: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('ensures the alert type gets validated for the license', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
updateAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.put.mock.calls[0];
|
||||
|
||||
rulesClient.update.mockRejectedValue(new RuleTypeDisabledError('Fail', 'license_invalid'));
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
'forbidden',
|
||||
]);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } });
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
updateAlertRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.put.mock.calls[0];
|
||||
rulesClient.update.mockResolvedValueOnce(mockedResponse as unknown as SanitizedRule);
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('update', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('does not return system actions', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
updateAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.put.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}"`);
|
||||
|
||||
rulesClient.update.mockResolvedValueOnce({
|
||||
...mockedResponse,
|
||||
systemActions: [systemAction],
|
||||
} as unknown as SanitizedRule);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
body: {
|
||||
throttle: null,
|
||||
name: 'abc',
|
||||
tags: ['bar'],
|
||||
schedule: { interval: '12s' },
|
||||
params: {
|
||||
otherField: false,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '2',
|
||||
params: {
|
||||
baz: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
},
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual({ body: mockedResponse });
|
||||
|
||||
expect(rulesClient.update).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.update.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data": Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"group": "default",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
"baz": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"name": "abc",
|
||||
"notifyWhen": "onActionGroupChange",
|
||||
"params": Object {
|
||||
"otherField": false,
|
||||
},
|
||||
"schedule": Object {
|
||||
"interval": "12s",
|
||||
},
|
||||
"tags": Array [
|
||||
"bar",
|
||||
],
|
||||
"throttle": null,
|
||||
},
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
updateAlertRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config] = router.put.mock.calls[0];
|
||||
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "PUT",
|
||||
"newApiPath": "/api/alerting/rule/rule/{id}",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,115 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { validateDurationSchema } from '../../lib';
|
||||
import { handleDisabledApiKeysError } from '../lib/error_handler';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import {
|
||||
RuleNotifyWhenType,
|
||||
LEGACY_BASE_ALERT_API_PATH,
|
||||
validateNotifyWhenType,
|
||||
} from '../../../common';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
const bodySchema = schema.object({
|
||||
name: schema.string(),
|
||||
tags: schema.arrayOf(schema.string(), { defaultValue: [] }),
|
||||
schedule: schema.object({
|
||||
interval: schema.string({ validate: validateDurationSchema }),
|
||||
}),
|
||||
throttle: schema.nullable(schema.string({ validate: validateDurationSchema })),
|
||||
params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
|
||||
actions: schema.arrayOf(
|
||||
schema.object({
|
||||
group: schema.string(),
|
||||
id: schema.string(),
|
||||
params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
|
||||
actionTypeId: schema.maybe(schema.string()),
|
||||
}),
|
||||
{ defaultValue: [] }
|
||||
),
|
||||
notifyWhen: schema.nullable(schema.string({ validate: validateNotifyWhenType })),
|
||||
});
|
||||
|
||||
export const updateAlertRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.put(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}`,
|
||||
validate: {
|
||||
body: bodySchema,
|
||||
params: paramSchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Update an alert',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'PUT',
|
||||
newApiPath: '/api/alerting/rule/rule/{id}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
handleDisabledApiKeysError(
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('update', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { id } = req.params;
|
||||
const { name, actions, params, schedule, tags, throttle, notifyWhen } = req.body;
|
||||
try {
|
||||
const { systemActions, ...alertRes } = await rulesClient.update({
|
||||
id,
|
||||
data: {
|
||||
name,
|
||||
actions,
|
||||
params,
|
||||
schedule,
|
||||
tags,
|
||||
throttle,
|
||||
notifyWhen: notifyWhen as RuleNotifyWhenType,
|
||||
},
|
||||
});
|
||||
return res.ok({
|
||||
body: alertRes,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof RuleTypeDisabledError) {
|
||||
return e.sendResponse(res);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
|
@ -1,144 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock';
|
||||
import { updateApiKeyRoute } from './update_api_key';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../rules_client.mock';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { docLinksServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/track_legacy_route_usage', () => ({
|
||||
trackLegacyRouteUsage: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('updateApiKeyRoute', () => {
|
||||
const docLinks = docLinksServiceMock.createSetupContract();
|
||||
|
||||
it('updates api key for an alert', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
updateApiKeyRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_update_api_key"`);
|
||||
expect(config.options?.access).toBe('public');
|
||||
|
||||
rulesClient.updateRuleApiKey.mockResolvedValueOnce();
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
['noContent']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toEqual(undefined);
|
||||
|
||||
expect(rulesClient.updateRuleApiKey).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClient.updateRuleApiKey.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(res.noContent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have internal access for serverless', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
updateApiKeyRoute(router, licenseState, docLinks, undefined, true);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_update_api_key"`);
|
||||
expect(config.options?.access).toBe('internal');
|
||||
});
|
||||
|
||||
it('ensures the alert type gets validated for the license', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
updateApiKeyRoute(router, licenseState, docLinks);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
rulesClient.updateRuleApiKey.mockRejectedValue(
|
||||
new RuleTypeDisabledError('Fail', 'license_invalid')
|
||||
);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
'forbidden',
|
||||
]);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } });
|
||||
});
|
||||
|
||||
it('should track every call', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
||||
updateApiKeyRoute(router, licenseState, docLinks, mockUsageCounter);
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [
|
||||
'ok',
|
||||
]);
|
||||
await handler(context, req, res);
|
||||
expect(trackLegacyRouteUsage).toHaveBeenCalledWith('updateApiKey', mockUsageCounter);
|
||||
});
|
||||
|
||||
it('should be deprecated', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
updateApiKeyRoute(router, licenseState, docLinks);
|
||||
|
||||
const [config] = router.post.mock.calls[0];
|
||||
expect(config.options?.deprecated).toMatchInlineSnapshot(
|
||||
{
|
||||
documentationUrl: expect.stringMatching(/#breaking-201550$/),
|
||||
},
|
||||
`
|
||||
Object {
|
||||
"documentationUrl": StringMatching /#breaking-201550\\$/,
|
||||
"reason": Object {
|
||||
"newApiMethod": "POST",
|
||||
"newApiPath": "/api/alerting/rule/{id}/_update_api_key",
|
||||
"type": "migrate",
|
||||
},
|
||||
"severity": "warning",
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,75 +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 { schema } from '@kbn/config-schema';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
import { DocLinksServiceSetup } from '@kbn/core/server';
|
||||
import type { AlertingRouter } from '../../types';
|
||||
import { ILicenseState } from '../../lib/license_state';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { LEGACY_BASE_ALERT_API_PATH } from '../../../common';
|
||||
import { handleDisabledApiKeysError } from '../lib/error_handler';
|
||||
import { RuleTypeDisabledError } from '../../lib/errors/rule_type_disabled';
|
||||
import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../constants';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string(),
|
||||
});
|
||||
|
||||
export const updateApiKeyRoute = (
|
||||
router: AlertingRouter,
|
||||
licenseState: ILicenseState,
|
||||
docLinks: DocLinksServiceSetup,
|
||||
usageCounter?: UsageCounter,
|
||||
isServerless?: boolean
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_update_api_key`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: isServerless ? 'internal' : 'public',
|
||||
summary: 'Update the API key for an alert',
|
||||
tags: ['oas-tag:alerting'],
|
||||
deprecated: {
|
||||
documentationUrl: docLinks.links.alerting.legacyRuleApiDeprecations,
|
||||
severity: 'warning',
|
||||
reason: {
|
||||
type: 'migrate',
|
||||
newApiMethod: 'POST',
|
||||
newApiPath: '/api/alerting/rule/{id}/_update_api_key',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
handleDisabledApiKeysError(
|
||||
router.handleLegacyErrors(async function (context, req, res) {
|
||||
verifyApiAccess(licenseState);
|
||||
if (!context.alerting) {
|
||||
return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' });
|
||||
}
|
||||
trackLegacyRouteUsage('updateApiKey', usageCounter);
|
||||
const alertingContext = await context.alerting;
|
||||
const rulesClient = await alertingContext.getRulesClient();
|
||||
const { id } = req.params;
|
||||
try {
|
||||
await rulesClient.updateRuleApiKey({ id });
|
||||
return res.noContent();
|
||||
} catch (e) {
|
||||
if (e instanceof RuleTypeDisabledError) {
|
||||
return e.sendResponse(res);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server';
|
||||
import { UserAtSpaceScenarios } from '../../../scenarios';
|
||||
import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
|
@ -199,7 +198,7 @@ export default function getAllConnectorTests({ getService }: FtrProviderContext)
|
|||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, RULE_SAVED_OBJECT_TYPE, 'alerts');
|
||||
objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(space.id)}/api/actions/connectors`)
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server';
|
||||
import { SuperuserAtSpace1, UserAtSpaceScenarios } from '../../../scenarios';
|
||||
import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
|
@ -244,7 +243,7 @@ export default function getAllConnectorTests({ getService }: FtrProviderContext)
|
|||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(space.id, createdAlert.id, RULE_SAVED_OBJECT_TYPE, 'alerts');
|
||||
objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(space.id)}/internal/actions/connectors`)
|
||||
|
|
|
@ -137,10 +137,10 @@ export default function preconfiguredAlertHistoryConnectorTests({
|
|||
expect().fail(`waiting for alert ${id} statuses ${Array.from(statuses)} timed out`);
|
||||
}
|
||||
|
||||
const response = await supertest.get(`/api/alerts/alert/${id}`);
|
||||
const response = await supertest.get(`/api/alerting/rule/${id}`);
|
||||
expect(response.status).to.eql(200);
|
||||
|
||||
const { executionStatus } = response.body || {};
|
||||
const { execution_status: executionStatus } = response.body || {};
|
||||
const { status } = executionStatus || {};
|
||||
|
||||
const message = `waitForStatus(${Array.from(statuses)}): got ${JSON.stringify(
|
||||
|
|
|
@ -720,104 +720,5 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', function () {
|
||||
this.tags('skipFIPS');
|
||||
it('should handle create alert request appropriately', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'MY action',
|
||||
connector_type_id: 'test.noop',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const {
|
||||
rule_type_id: alertTypeId,
|
||||
notify_when: notifyWhen,
|
||||
...testAlert
|
||||
} = getTestRuleData({
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
...testAlert,
|
||||
alertTypeId,
|
||||
notifyWhen,
|
||||
});
|
||||
|
||||
expect(response.status).to.eql(200);
|
||||
objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting');
|
||||
expect(response.body).to.eql({
|
||||
id: response.body.id,
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
actionTypeId: createdAction.connector_type_id,
|
||||
group: 'default',
|
||||
params: {},
|
||||
uuid: response.body.actions[0].uuid,
|
||||
},
|
||||
],
|
||||
enabled: true,
|
||||
alertTypeId: 'test.noop',
|
||||
consumer: 'alertsFixture',
|
||||
params: {},
|
||||
createdBy: null,
|
||||
schedule: { interval: '1m' },
|
||||
scheduledTaskId: response.body.scheduledTaskId,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
throttle: '1m',
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
createdAt: response.body.createdAt,
|
||||
updatedAt: response.body.updatedAt,
|
||||
executionStatus: response.body.executionStatus,
|
||||
revision: 0,
|
||||
running: false,
|
||||
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
|
||||
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
|
||||
});
|
||||
expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0);
|
||||
expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0);
|
||||
expect(Date.parse(response.body.updatedAt)).to.eql(Date.parse(response.body.createdAt));
|
||||
if (response.body.next_run) {
|
||||
expect(Date.parse(response.body.next_run)).to.be.greaterThan(0);
|
||||
}
|
||||
expect(typeof response.body.scheduledTaskId).to.be('string');
|
||||
const taskRecord = await getScheduledTask(response.body.scheduledTaskId);
|
||||
expect(taskRecord.type).to.eql('task');
|
||||
expect(taskRecord.task.taskType).to.eql('alerting:test.noop');
|
||||
expect(JSON.parse(taskRecord.task.params)).to.eql({
|
||||
alertId: response.body.id,
|
||||
spaceId: Spaces.space1.id,
|
||||
consumer: 'alertsFixture',
|
||||
});
|
||||
expect(taskRecord.task.enabled).to.eql(true);
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: response.body.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -65,27 +65,5 @@ export default function createDeleteTests({ getService }: FtrProviderContext) {
|
|||
message: `Saved object [alert/${createdAlert.id}] not found`,
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', () => {
|
||||
it('should handle delete alert request appropriately', async () => {
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData())
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.delete(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(204, '');
|
||||
|
||||
try {
|
||||
await getScheduledTask(createdAlert.scheduledTaskId);
|
||||
throw new Error('Should have removed scheduled task');
|
||||
} catch (e) {
|
||||
expect(e.meta.statusCode).to.eql(404);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -266,50 +266,5 @@ export default function createDisableRuleTests({ getService }: FtrProviderContex
|
|||
id: createdRule.id,
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', function () {
|
||||
it('should handle disable rule request appropriately', async () => {
|
||||
const { body: createdRule } = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData({ enabled: true }))
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting');
|
||||
|
||||
await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdRule.id}/_disable`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(204);
|
||||
|
||||
// task doc should still exist but be disabled
|
||||
await retry.try(async () => {
|
||||
const taskRecord = await getScheduledTask(createdRule.scheduled_task_id);
|
||||
expect(taskRecord.type).to.eql('task');
|
||||
expect(taskRecord.task.taskType).to.eql('alerting:test.noop');
|
||||
expect(JSON.parse(taskRecord.task.params)).to.eql({
|
||||
alertId: createdRule.id,
|
||||
spaceId: Spaces.space1.id,
|
||||
consumer: 'alertsFixture',
|
||||
});
|
||||
expect(taskRecord.task.enabled).to.eql(false);
|
||||
});
|
||||
|
||||
const { body: disabledRule } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdRule.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
|
||||
// Ensure revision was not updated
|
||||
expect(disabledRule.revision).to.eql(0);
|
||||
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest: supertestWithoutAuth,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdRule.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -94,48 +94,5 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', function () {
|
||||
this.tags('skipFIPS');
|
||||
it('should handle enable alert request appropriately', async () => {
|
||||
const { body: createdAlert } = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData({ enabled: false }))
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_enable`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(204);
|
||||
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
expect(typeof updatedAlert.scheduled_task_id).to.eql('string');
|
||||
const taskRecord = await getScheduledTask(updatedAlert.scheduled_task_id);
|
||||
expect(taskRecord.type).to.eql('task');
|
||||
expect(taskRecord.task.taskType).to.eql('alerting:test.noop');
|
||||
expect(JSON.parse(taskRecord.task.params)).to.eql({
|
||||
alertId: createdAlert.id,
|
||||
spaceId: Spaces.space1.id,
|
||||
consumer: 'alertsFixture',
|
||||
});
|
||||
expect(taskRecord.task.enabled).to.eql(true);
|
||||
|
||||
// Ensure revision was not updated
|
||||
expect(updatedAlert.revision).to.eql(0);
|
||||
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest: supertestWithoutAuth,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -336,58 +336,5 @@ export default function createFindTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', function () {
|
||||
this.tags('skipFIPS');
|
||||
it('should handle find alert request appropriately', async () => {
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData())
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await supertest.get(
|
||||
`${getUrlPrefix(
|
||||
Spaces.space1.id
|
||||
)}/api/alerts/_find?search=test.noop&search_fields=alertTypeId`
|
||||
);
|
||||
|
||||
expect(response.status).to.eql(200);
|
||||
expect(response.body.page).to.equal(1);
|
||||
expect(response.body.perPage).to.be.greaterThan(0);
|
||||
expect(response.body.total).to.be.greaterThan(0);
|
||||
const match = response.body.data.find((obj: any) => obj.id === createdAlert.id);
|
||||
expect(match).to.eql({
|
||||
id: createdAlert.id,
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
alertTypeId: 'test.noop',
|
||||
consumer: 'alertsFixture',
|
||||
schedule: { interval: '1m' },
|
||||
enabled: true,
|
||||
actions: [],
|
||||
params: {},
|
||||
createdBy: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
scheduledTaskId: match.scheduledTaskId,
|
||||
updatedBy: null,
|
||||
throttle: '1m',
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
createdAt: match.createdAt,
|
||||
updatedAt: match.updatedAt,
|
||||
executionStatus: match.executionStatus,
|
||||
revision: 0,
|
||||
running: false,
|
||||
...(match.nextRun ? { nextRun: match.nextRun } : {}),
|
||||
...(match.lastRun ? { lastRun: match.lastRun } : {}),
|
||||
});
|
||||
expect(Date.parse(match.createdAt)).to.be.greaterThan(0);
|
||||
expect(Date.parse(match.updatedAt)).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -337,54 +337,5 @@ export default function createFindTests({ getService }: FtrProviderContext) {
|
|||
expect(response.body.total).to.equal(1);
|
||||
expect(response.body.data[0].consumer).to.eql('alertsRestrictedFixture');
|
||||
});
|
||||
|
||||
describe('legacy', function () {
|
||||
this.tags('skipFIPS');
|
||||
it('should handle find alert request appropriately', async () => {
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData())
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/_find`);
|
||||
|
||||
expect(response.status).to.eql(200);
|
||||
expect(response.body.page).to.equal(1);
|
||||
expect(response.body.perPage).to.be.greaterThan(0);
|
||||
expect(response.body.total).to.be.greaterThan(0);
|
||||
const match = response.body.data.find((obj: any) => obj.id === createdAlert.id);
|
||||
expect(match).to.eql({
|
||||
id: createdAlert.id,
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
alertTypeId: 'test.noop',
|
||||
consumer: 'alertsFixture',
|
||||
schedule: { interval: '1m' },
|
||||
enabled: true,
|
||||
actions: [],
|
||||
params: {},
|
||||
createdBy: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
scheduledTaskId: match.scheduledTaskId,
|
||||
updatedBy: null,
|
||||
throttle: '1m',
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
createdAt: match.createdAt,
|
||||
updatedAt: match.updatedAt,
|
||||
executionStatus: match.executionStatus,
|
||||
revision: 0,
|
||||
running: false,
|
||||
...(match.nextRun ? { nextRun: match.nextRun } : {}),
|
||||
...(match.lastRun ? { lastRun: match.lastRun } : {}),
|
||||
});
|
||||
expect(Date.parse(match.createdAt)).to.be.greaterThan(0);
|
||||
expect(Date.parse(match.updatedAt)).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -124,55 +124,5 @@ export default function createGetTests({ getService }: FtrProviderContext) {
|
|||
|
||||
getTestUtils('public', objectRemover, supertest);
|
||||
getTestUtils('internal', objectRemover, supertest);
|
||||
|
||||
describe('legacy', function () {
|
||||
this.tags('skipFIPS');
|
||||
it('should handle get alert request appropriately', async () => {
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData())
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const response = await supertest.get(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`
|
||||
);
|
||||
|
||||
expect(response.status).to.eql(200);
|
||||
expect(response.body).to.eql({
|
||||
id: createdAlert.id,
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
alertTypeId: 'test.noop',
|
||||
consumer: 'alertsFixture',
|
||||
schedule: { interval: '1m' },
|
||||
enabled: true,
|
||||
actions: [],
|
||||
params: {},
|
||||
createdBy: null,
|
||||
scheduledTaskId: response.body.scheduledTaskId,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
throttle: '1m',
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
createdAt: response.body.createdAt,
|
||||
updatedAt: response.body.updatedAt,
|
||||
executionStatus: response.body.executionStatus,
|
||||
revision: 0,
|
||||
running: false,
|
||||
...(response.body.nextRun ? { nextRun: response.body.nextRun } : {}),
|
||||
...(response.body.lastRun ? { lastRun: response.body.lastRun } : {}),
|
||||
});
|
||||
expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0);
|
||||
expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0);
|
||||
if (response.body.nextRun) {
|
||||
expect(Date.parse(response.body.nextRun)).to.be.greaterThan(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -91,53 +91,5 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont
|
|||
message: 'Saved object [alert/1] not found',
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', () => {
|
||||
it('should fetch updated state', async () => {
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
enabled: true,
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
rule_type_id: 'test.cumulative-firing',
|
||||
consumer: 'alertsFixture',
|
||||
schedule: { interval: '5s' },
|
||||
throttle: '5s',
|
||||
actions: [],
|
||||
params: {},
|
||||
notify_when: 'onThrottleInterval',
|
||||
})
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
// wait for alert to actually execute
|
||||
await retry.try(async () => {
|
||||
const response = await supertest.get(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/state`
|
||||
);
|
||||
|
||||
expect(response.status).to.eql(200);
|
||||
expect(response.body).to.key('alertInstances', 'alertTypeState', 'previousStartedAt');
|
||||
expect(response.body.alertTypeState.runCount).to.greaterThan(1);
|
||||
});
|
||||
|
||||
const response = await supertest.get(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}/state`
|
||||
);
|
||||
|
||||
expect(response.body.rule_type_state.runCount).to.greaterThan(0);
|
||||
|
||||
const alertInstances = Object.entries<Record<string, any>>(response.body.alerts);
|
||||
expect(alertInstances.length).to.eql(response.body.rule_type_state.runCount);
|
||||
alertInstances.forEach(([key, value], index) => {
|
||||
expect(key).to.eql(`instance-${index}`);
|
||||
expect(value.state.instanceStateValue).to.be(true);
|
||||
expect(value.state.start).not.to.be(undefined);
|
||||
expect(value.state.duration).not.to.be(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -384,78 +384,6 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo
|
|||
expect(actualAlerts).to.eql(expectedAlerts);
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', function () {
|
||||
this.tags('skipFIPS');
|
||||
it('handles multi-alert status', async () => {
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
|
||||
// pattern of when the alert should fire
|
||||
const pattern = {
|
||||
alertA: [true, true, true, true],
|
||||
alertB: [true, true, false, false],
|
||||
alertC: [true, true, true, true],
|
||||
};
|
||||
|
||||
const { body: createdRule } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
rule_type_id: 'test.patternFiring',
|
||||
params: { pattern },
|
||||
schedule: { interval: '1s' },
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting');
|
||||
|
||||
await alertUtils.muteInstance(createdRule.id, 'alertC');
|
||||
await alertUtils.muteInstance(createdRule.id, 'alertD');
|
||||
await waitForEvents(createdRule.id, ['new-instance', 'recovered-instance']);
|
||||
const response = await supertest.get(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdRule.id}/_instance_summary`
|
||||
);
|
||||
|
||||
const actualAlerts = checkAndCleanActualAlerts(response.body.instances, [
|
||||
'alertA',
|
||||
'alertB',
|
||||
'alertC',
|
||||
]);
|
||||
const expectedAlerts = {
|
||||
alertA: {
|
||||
status: 'Active',
|
||||
muted: false,
|
||||
actionGroupId: 'default',
|
||||
activeStartDate: actualAlerts.alertA.activeStartDate,
|
||||
flapping: false,
|
||||
tracked: true,
|
||||
},
|
||||
alertB: {
|
||||
status: 'OK',
|
||||
muted: false,
|
||||
flapping: false,
|
||||
tracked: true,
|
||||
},
|
||||
alertC: {
|
||||
status: 'Active',
|
||||
muted: true,
|
||||
actionGroupId: 'default',
|
||||
activeStartDate: actualAlerts.alertC.activeStartDate,
|
||||
flapping: false,
|
||||
tracked: true,
|
||||
},
|
||||
alertD: {
|
||||
status: 'OK',
|
||||
muted: true,
|
||||
flapping: false,
|
||||
tracked: true,
|
||||
},
|
||||
};
|
||||
expect(actualAlerts).to.eql(expectedAlerts);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function waitForEvents(id: string, actions: string[]) {
|
||||
|
|
|
@ -103,46 +103,5 @@ export default function listRuleTypes({ getService }: FtrProviderContext) {
|
|||
params: [],
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', () => {
|
||||
it('should return 200 with list of alert types', async () => {
|
||||
const response = await supertest.get(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types`
|
||||
);
|
||||
expect(response.status).to.eql(200);
|
||||
const { authorizedConsumers, ...fixtureAlertType } = response.body.find(
|
||||
(alertType: any) => alertType.id === 'test.noop'
|
||||
);
|
||||
expect(fixtureAlertType).to.eql({
|
||||
actionGroups: [
|
||||
{ id: 'default', name: 'Default' },
|
||||
{ id: 'recovered', name: 'Recovered' },
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
doesSetRecoveryContext: false,
|
||||
id: 'test.noop',
|
||||
name: 'Test: Noop',
|
||||
actionVariables: {
|
||||
state: [],
|
||||
params: [],
|
||||
context: [],
|
||||
},
|
||||
recoveryActionGroup: {
|
||||
id: 'recovered',
|
||||
name: 'Recovered',
|
||||
},
|
||||
category: 'kibana',
|
||||
producer: 'alertsFixture',
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: true,
|
||||
enabledInLicense: true,
|
||||
hasFieldsForAAD: false,
|
||||
hasAlertsMappings: false,
|
||||
ruleTaskTimeout: '5m',
|
||||
validLegacyConsumers: ['alerts'],
|
||||
});
|
||||
expect(Object.keys(authorizedConsumers)).to.contain('alertsFixture');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -51,35 +51,5 @@ export default function createMuteTests({ getService }: FtrProviderContext) {
|
|||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', () => {
|
||||
it('should handle mute alert request appropriately', async () => {
|
||||
const { body: createdAlert } = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData({ enabled: false }))
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_mute_all`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(204);
|
||||
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
expect(updatedAlert.mute_all).to.eql(true);
|
||||
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest: supertestWithoutAuth,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -52,39 +52,5 @@ export default function createMuteInstanceTests({ getService }: FtrProviderConte
|
|||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', () => {
|
||||
it('should handle mute alert instance request appropriately', async () => {
|
||||
const { body: createdAlert } = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData({ enabled: false }))
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
await supertestWithoutAuth
|
||||
.post(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${
|
||||
createdAlert.id
|
||||
}/alert_instance/1/_mute`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(204);
|
||||
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
expect(updatedAlert.muted_alert_ids).to.eql(['1']);
|
||||
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest: supertestWithoutAuth,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -53,39 +53,5 @@ export default function createUnmuteTests({ getService }: FtrProviderContext) {
|
|||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', () => {
|
||||
it('should handle unmute alert request appropriately', async () => {
|
||||
const { body: createdAlert } = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData({ enabled: false }))
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_mute_all`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(204);
|
||||
await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_unmute_all`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(204);
|
||||
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
expect(updatedAlert.mute_all).to.eql(false);
|
||||
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest: supertestWithoutAuth,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -53,47 +53,5 @@ export default function createUnmuteInstanceTests({ getService }: FtrProviderCon
|
|||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', () => {
|
||||
it('should handle unmute alert instance request appropriately', async () => {
|
||||
const { body: createdAlert } = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData({ enabled: false }))
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
await supertestWithoutAuth
|
||||
.post(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${
|
||||
createdAlert.id
|
||||
}/alert_instance/1/_mute`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(204);
|
||||
await supertestWithoutAuth
|
||||
.post(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${
|
||||
createdAlert.id
|
||||
}/alert_instance/1/_unmute`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(204);
|
||||
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
expect(updatedAlert.muted_alert_ids).to.eql([]);
|
||||
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest: supertestWithoutAuth,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -408,75 +408,5 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', function () {
|
||||
this.tags('skipFIPS');
|
||||
it('should handle update alert request appropriately', async () => {
|
||||
const { body: createdAlert } = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData())
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
const updatedData = {
|
||||
name: 'bcd',
|
||||
tags: ['bar'],
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
schedule: { interval: '12s' },
|
||||
actions: [],
|
||||
throttle: '1m',
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
};
|
||||
|
||||
const response = await supertest
|
||||
.put(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(updatedData)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.eql({
|
||||
...updatedData,
|
||||
id: createdAlert.id,
|
||||
tags: ['bar'],
|
||||
alertTypeId: 'test.noop',
|
||||
consumer: 'alertsFixture',
|
||||
createdBy: null,
|
||||
enabled: true,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
scheduledTaskId: createdAlert.scheduled_task_id,
|
||||
createdAt: response.body.createdAt,
|
||||
updatedAt: response.body.updatedAt,
|
||||
executionStatus: response.body.executionStatus,
|
||||
revision: 1,
|
||||
running: false,
|
||||
...(response.body.nextRun ? { nextRun: response.body.nextRun } : {}),
|
||||
...(response.body.lastRun ? { lastRun: response.body.lastRun } : {}),
|
||||
});
|
||||
expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0);
|
||||
expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0);
|
||||
expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(
|
||||
Date.parse(response.body.createdAt)
|
||||
);
|
||||
if (response.body.nextRun) {
|
||||
expect(Date.parse(response.body.nextRun)).to.be.greaterThan(0);
|
||||
}
|
||||
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -79,41 +79,5 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', function () {
|
||||
this.tags('skipFIPS');
|
||||
it('should handle update alert api key appropriately', async () => {
|
||||
const { body: createdAlert } = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData())
|
||||
.expect(200);
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
|
||||
|
||||
await supertestWithoutAuth
|
||||
.post(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_update_api_key`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(204);
|
||||
|
||||
const { body: updatedAlert } = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
expect(updatedAlert.api_key_owner).to.eql(null);
|
||||
|
||||
// Ensure revision is not incremented when API key is updated
|
||||
expect(updatedAlert.revision).to.eql(0);
|
||||
|
||||
// Ensure AAD isn't broken
|
||||
await checkAAD({
|
||||
supertest: supertestWithoutAuth,
|
||||
spaceId: Spaces.space1.id,
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
id: createdAlert.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,12 +10,12 @@ export function MonitoringAlertsProvider({ getService }) {
|
|||
|
||||
return new (class MonitoringAlerts {
|
||||
async deleteAlerts() {
|
||||
const apiResponse = await supertest.get('/api/alerts/_find?per_page=20');
|
||||
const apiResponse = await supertest.get('/api/alerting/rules/_find?per_page=20');
|
||||
const alerts = apiResponse.body.data.filter(({ consumer }) => consumer === 'monitoring');
|
||||
|
||||
return await Promise.all(
|
||||
alerts.map(async (alert) =>
|
||||
supertest.delete(`/api/alerts/alert/${alert.id}`).set('kbn-xsrf', 'true').expect(204)
|
||||
supertest.delete(`/api/alerting/rule/${alert.id}`).set('kbn-xsrf', 'true').expect(204)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
await retry.tryForTime(60 * 1000, async () => {
|
||||
// add a delay before next call to not overload the server
|
||||
await setTimeoutAsync(1500);
|
||||
const apiResponse = await supertest.get('/api/alerts/_find?search=uptime-test');
|
||||
const apiResponse = await supertest.get('/api/alerting/rules/_find?search=uptime-test');
|
||||
const alertsFromThisTest = apiResponse.body.data.filter(
|
||||
({ name }: { name: string }) => name === 'uptime-test'
|
||||
);
|
||||
|
@ -111,7 +111,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
// for our test helper to input into the flyout.
|
||||
const {
|
||||
actions,
|
||||
alertTypeId,
|
||||
rule_type_id: alertTypeId,
|
||||
consumer,
|
||||
id,
|
||||
params: { numTimes, timerangeUnit, timerangeCount, filters },
|
||||
|
@ -134,7 +134,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
`{"tags":[],"url.port":["5678"],"observer.geo.name":["mpls"],"monitor.type":["http"]}`
|
||||
);
|
||||
} finally {
|
||||
await supertest.delete(`/api/alerts/alert/${id}`).set('kbn-xsrf', 'true').expect(204);
|
||||
await supertest.delete(`/api/alerting/rule/${id}`).set('kbn-xsrf', 'true').expect(204);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -178,7 +178,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
it('has created a valid alert with expected parameters', async () => {
|
||||
let alert: any;
|
||||
await retry.tryForTime(60 * 1000, async () => {
|
||||
const apiResponse = await supertest.get(`/api/alerts/_find?search=${alertId}`);
|
||||
const apiResponse = await supertest.get(`/api/alerting/rules/_find?search=${alertId}`);
|
||||
const alertsFromThisTest = apiResponse.body.data.filter(
|
||||
({ name }: { name: string }) => name === alertId
|
||||
);
|
||||
|
@ -191,7 +191,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
// for our test helper to input into the flyout.
|
||||
const {
|
||||
actions,
|
||||
alertTypeId,
|
||||
rule_type_id: alertTypeId,
|
||||
consumer,
|
||||
id,
|
||||
params,
|
||||
|
@ -206,7 +206,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
expect(params).to.eql({});
|
||||
expect(interval).to.eql('11m');
|
||||
} finally {
|
||||
await supertest.delete(`/api/alerts/alert/${id}`).set('kbn-xsrf', 'true').expect(204);
|
||||
await supertest.delete(`/api/alerting/rule/${id}`).set('kbn-xsrf', 'true').expect(204);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -83,7 +83,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
it('has created a valid simple alert with expected parameters', async () => {
|
||||
let alert: any;
|
||||
await retry.tryForTime(15000, async () => {
|
||||
const apiResponse = await supertest.get(`/api/alerts/_find?search=Simple status alert`);
|
||||
const apiResponse = await supertest.get(
|
||||
`/api/alerting/rules/_find?search=Simple status alert`
|
||||
);
|
||||
const alertsFromThisTest = apiResponse.body.data.filter(({ params }: { params: any }) =>
|
||||
params.search.includes(monitorId)
|
||||
);
|
||||
|
@ -91,10 +93,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
alert = alertsFromThisTest[0];
|
||||
});
|
||||
|
||||
const { actions, alertTypeId, consumer, tags } = alert ?? {};
|
||||
const { actions, rule_type_id: alertTypeId, consumer, tags } = alert ?? {};
|
||||
expect(actions).to.eql([
|
||||
{
|
||||
actionTypeId: '.slack',
|
||||
connector_type_id: '.slack',
|
||||
group: 'recovered',
|
||||
params: {
|
||||
message: MonitorStatusTranslations.defaultRecoveryMessage,
|
||||
|
@ -103,7 +105,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
uuid: actions[0].uuid,
|
||||
},
|
||||
{
|
||||
actionTypeId: '.slack',
|
||||
connector_type_id: '.slack',
|
||||
group: 'xpack.uptime.alerts.actionGroups.monitorStatus',
|
||||
params: {
|
||||
message: MonitorStatusTranslations.defaultActionMessage,
|
||||
|
|
|
@ -321,7 +321,7 @@ export default ({ getPageObjects, getPageObject, getService }: FtrProviderContex
|
|||
},
|
||||
supertest
|
||||
);
|
||||
objectRemover.add(rule.id, 'alert', 'alerts');
|
||||
objectRemover.add(rule.id, 'rule', 'alerting');
|
||||
|
||||
// refresh to see rule
|
||||
await browser.refresh();
|
||||
|
|
|
@ -115,7 +115,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
|
||||
const getRuleIdByName = async (name: string) => {
|
||||
const response = await supertest
|
||||
.get(`/api/alerts/_find?search=${name}&search_fields=name`)
|
||||
.get(`/api/alerting/rules/_find?search=${name}&search_fields=name`)
|
||||
.expect(200);
|
||||
return response.body.data[0].id;
|
||||
};
|
||||
|
|
|
@ -47,7 +47,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestAlertData(overwrites))
|
||||
.expect(200);
|
||||
objectRemover.add(createdRule.id, 'alert', 'alerts');
|
||||
objectRemover.add(createdRule.id, 'rule', 'alerting');
|
||||
return createdRule;
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
})
|
||||
)
|
||||
.expect(200);
|
||||
objectRemover.add(createdRule.id, 'alert', 'alerts');
|
||||
objectRemover.add(createdRule.id, 'rule', 'alerting');
|
||||
return createdRule;
|
||||
}
|
||||
|
||||
|
|
|
@ -223,7 +223,7 @@ export default ({ getPageObjects, getPageObject, getService }: FtrProviderContex
|
|||
)
|
||||
.expect(200);
|
||||
|
||||
objectRemover.add(createdRule.id, 'alert', 'alerts');
|
||||
objectRemover.add(createdRule.id, 'rule', 'alerting');
|
||||
|
||||
await retry.try(async () => {
|
||||
const { alerts: alertInstances } = await getAlertSummary(createdRule.id);
|
||||
|
@ -265,7 +265,7 @@ export default ({ getPageObjects, getPageObject, getService }: FtrProviderContex
|
|||
)
|
||||
.expect(200);
|
||||
|
||||
objectRemover.add(createdRule.id, 'alert', 'alerts');
|
||||
objectRemover.add(createdRule.id, 'rule', 'alerting');
|
||||
|
||||
await retry.try(async () => {
|
||||
const { alerts: alertInstances } = await getAlertSummary(createdRule.id);
|
||||
|
|
|
@ -51,7 +51,7 @@ export async function createAlert({
|
|||
overwrites?: Record<string, any>;
|
||||
}) {
|
||||
const createdAlert = await createAlertManualCleanup({ supertest, overwrites });
|
||||
objectRemover.add(createdAlert.id, 'alert', 'alerts');
|
||||
objectRemover.add(createdAlert.id, 'rule', 'alerting');
|
||||
return createdAlert;
|
||||
}
|
||||
|
||||
|
|
|
@ -301,7 +301,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
// update apiKey to fix decryption error
|
||||
await request
|
||||
.post(`/api/alerts/alert/${ruleId}/_update_api_key`)
|
||||
.post(`/api/alerting/rule/${ruleId}/_update_api_key`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.expect(204);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export const createAlert = async (
|
|||
) => {
|
||||
const supertest = getService('supertestWithoutAuth');
|
||||
const { body: response, status } = await supertest
|
||||
.post(`${getSpaceUrlPrefix(spaceId)}/api/alerts/alert`)
|
||||
.post(`${getSpaceUrlPrefix(spaceId)}/api/alerting/rule`)
|
||||
.auth(user.username, user.password)
|
||||
.send(alertDef)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
|
|
|
@ -22,7 +22,7 @@ export const deleteAlert = async (
|
|||
const { body: targetIndices } = await getAlertsTargetIndices(getService, user, spaceId);
|
||||
if (id) {
|
||||
const { body, status } = await supertest
|
||||
.delete(`${getSpaceUrlPrefix(spaceId)}/api/alerts/alert/${id}`)
|
||||
.delete(`${getSpaceUrlPrefix(spaceId)}/api/alerting/rule/${id}`)
|
||||
.auth(user.username, user.password)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ export async function waitUntilNextExecution(
|
|||
});
|
||||
|
||||
const { body, status } = await supertest
|
||||
.get(`${getSpaceUrlPrefix(spaceId)}/api/alerts/alert/${alert.id}`)
|
||||
.get(`${getSpaceUrlPrefix(spaceId)}/api/alerting/rule/${alert.id}`)
|
||||
.auth(user.username, user.password)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
|
||||
|
|
|
@ -17,7 +17,9 @@ export const waitForAlertToComplete = async (
|
|||
): Promise<void> => {
|
||||
await waitFor(
|
||||
async () => {
|
||||
const response = await supertest.get(`/api/alerts/alert/${id}/state`).set('kbn-xsrf', 'true');
|
||||
const response = await supertest
|
||||
.get(`/internal/alerting/rule/${id}/state`)
|
||||
.set('kbn-xsrf', 'true');
|
||||
if (response.status !== 200) {
|
||||
log.debug(
|
||||
`Did not get an expected 200 "ok" when waiting for an alert to complete (waitForAlertToComplete). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify(
|
||||
|
@ -25,7 +27,7 @@ export const waitForAlertToComplete = async (
|
|||
)}, status: ${JSON.stringify(response.status)}`
|
||||
);
|
||||
}
|
||||
return response.body.previousStartedAt != null;
|
||||
return response.body.previous_started_at != null;
|
||||
},
|
||||
'waitForAlertToComplete',
|
||||
log
|
||||
|
|
|
@ -296,7 +296,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
// update apiKey to fix decryption error
|
||||
await request
|
||||
.post(`/api/alerts/alert/${ruleId}/_update_api_key`)
|
||||
.post(`/api/alerting/rule/${ruleId}/_update_api_key`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.expect(204);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue