[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:
Janki Salvi 2024-12-16 14:35:28 +00:00 committed by GitHub
parent 3a6d27af37
commit 279f4aec6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
82 changed files with 36 additions and 9761 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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