mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Response Ops][Flapping] Rule Specific Flapping - Create/Update API changes (#190019)
## Summary Issue: https://github.com/elastic/kibana/issues/190018 Implement rule specific flapping support for create and update Rule API. The new property on the rule is named `flapping`; ``` flapping: { look_back_window: number; status_change_threshold: number; } ``` Also make changes in the task runner to use the rule's flapping settings if it exists. Otherwise use the global flapping setting. # To test 1. Go to `x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts` and turn `IS_RULE_SPECIFIC_FLAPPING_ENABLED` to `true` 2. Create a rule with a rule specific flapping setting, generate the alert and let it flap 3. Assert that the flapping is now using the rule specific flapping 4. Turn space flapping off 5. Assert that it no longer flaps despite having a rule specific flapping 6. Try deleting/adding back the rule specific flapping via the UI and verify everything works. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e9aff7dccd
commit
edd61f63db
80 changed files with 2108 additions and 74 deletions
|
@ -1851,6 +1851,27 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"description": "The identifier for the rule.",
|
||||
"type": "string"
|
||||
|
@ -2667,6 +2688,27 @@
|
|||
"description": "Indicates whether you want to run the rule on an interval basis after it is created.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the rule. While this name does not have to be unique, a distinctive name can help you identify a rule.",
|
||||
"type": "string"
|
||||
|
@ -3047,6 +3089,27 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"description": "The identifier for the rule.",
|
||||
"type": "string"
|
||||
|
@ -3853,6 +3916,27 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the rule. While this name does not have to be unique, a distinctive name can help you identify a rule.",
|
||||
"type": "string"
|
||||
|
@ -4226,6 +4310,27 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"description": "The identifier for the rule.",
|
||||
"type": "string"
|
||||
|
@ -5692,6 +5797,27 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"description": "The identifier for the rule.",
|
||||
"type": "string"
|
||||
|
|
|
@ -1851,6 +1851,27 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"description": "The identifier for the rule.",
|
||||
"type": "string"
|
||||
|
@ -2667,6 +2688,27 @@
|
|||
"description": "Indicates whether you want to run the rule on an interval basis after it is created.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the rule. While this name does not have to be unique, a distinctive name can help you identify a rule.",
|
||||
"type": "string"
|
||||
|
@ -3047,6 +3089,27 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"description": "The identifier for the rule.",
|
||||
"type": "string"
|
||||
|
@ -3853,6 +3916,27 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the rule. While this name does not have to be unique, a distinctive name can help you identify a rule.",
|
||||
"type": "string"
|
||||
|
@ -4226,6 +4310,27 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"description": "The identifier for the rule.",
|
||||
"type": "string"
|
||||
|
@ -5692,6 +5797,27 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"flapping": {
|
||||
"additionalProperties": false,
|
||||
"nullable": true,
|
||||
"properties": {
|
||||
"look_back_window": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
},
|
||||
"status_change_threshold": {
|
||||
"maximum": 20,
|
||||
"minimum": 2,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"look_back_window",
|
||||
"status_change_threshold"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"description": "The identifier for the rule.",
|
||||
"type": "string"
|
||||
|
|
|
@ -1269,6 +1269,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -2026,6 +2042,22 @@ paths:
|
|||
Indicates whether you want to run the rule on an interval
|
||||
basis after it is created.
|
||||
type: boolean
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
name:
|
||||
description: >-
|
||||
The name of the rule. While this name does not have to be
|
||||
|
@ -2410,6 +2442,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -3146,6 +3194,22 @@ paths:
|
|||
type: number
|
||||
required:
|
||||
- active
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
name:
|
||||
description: >-
|
||||
The name of the rule. While this name does not have to be
|
||||
|
@ -3522,6 +3586,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -4732,6 +4812,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
|
|
@ -1269,6 +1269,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -2026,6 +2042,22 @@ paths:
|
|||
Indicates whether you want to run the rule on an interval
|
||||
basis after it is created.
|
||||
type: boolean
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
name:
|
||||
description: >-
|
||||
The name of the rule. While this name does not have to be
|
||||
|
@ -2410,6 +2442,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -3146,6 +3194,22 @@ paths:
|
|||
type: number
|
||||
required:
|
||||
- active
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
name:
|
||||
description: >-
|
||||
The name of the rule. While this name does not have to be
|
||||
|
@ -3522,6 +3586,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -4732,6 +4812,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
|
|
@ -1650,6 +1650,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -2407,6 +2423,22 @@ paths:
|
|||
Indicates whether you want to run the rule on an interval
|
||||
basis after it is created.
|
||||
type: boolean
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
name:
|
||||
description: >-
|
||||
The name of the rule. While this name does not have to be
|
||||
|
@ -2791,6 +2823,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -3527,6 +3575,22 @@ paths:
|
|||
type: number
|
||||
required:
|
||||
- active
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
name:
|
||||
description: >-
|
||||
The name of the rule. While this name does not have to be
|
||||
|
@ -3903,6 +3967,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -5113,6 +5193,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
|
|
@ -1650,6 +1650,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -2407,6 +2423,22 @@ paths:
|
|||
Indicates whether you want to run the rule on an interval
|
||||
basis after it is created.
|
||||
type: boolean
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
name:
|
||||
description: >-
|
||||
The name of the rule. While this name does not have to be
|
||||
|
@ -2791,6 +2823,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -3527,6 +3575,22 @@ paths:
|
|||
type: number
|
||||
required:
|
||||
- active
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
name:
|
||||
description: >-
|
||||
The name of the rule. While this name does not have to be
|
||||
|
@ -3903,6 +3967,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
@ -5113,6 +5193,22 @@ paths:
|
|||
required:
|
||||
- status
|
||||
- last_execution_date
|
||||
flapping:
|
||||
additionalProperties: false
|
||||
nullable: true
|
||||
type: object
|
||||
properties:
|
||||
look_back_window:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
status_change_threshold:
|
||||
maximum: 20
|
||||
minimum: 2
|
||||
type: number
|
||||
required:
|
||||
- look_back_window
|
||||
- status_change_threshold
|
||||
id:
|
||||
description: The identifier for the rule.
|
||||
type: string
|
||||
|
|
10
packages/kbn-alerting-types/flapping/latest.ts
Normal file
10
packages/kbn-alerting-types/flapping/latest.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export * from './v1';
|
|
@ -18,5 +18,4 @@ export * from './r_rule_types';
|
|||
export * from './rule_notify_when_type';
|
||||
export * from './rule_type_types';
|
||||
export * from './rule_types';
|
||||
export * from './rule_flapping';
|
||||
export * from './search_strategy_types';
|
||||
|
|
|
@ -205,6 +205,11 @@ export type SanitizedRuleAction = Omit<RuleAction, 'alertsFilter'> & {
|
|||
alertsFilter?: SanitizedAlertsFilter;
|
||||
};
|
||||
|
||||
export interface Flapping extends SavedObjectAttributes {
|
||||
lookBackWindow: number;
|
||||
statusChangeThreshold: number;
|
||||
}
|
||||
|
||||
export interface Rule<Params extends RuleTypeParams = never> {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
|
@ -240,10 +245,7 @@ export interface Rule<Params extends RuleTypeParams = never> {
|
|||
running?: boolean | null;
|
||||
viewInAppRelativeUrl?: string;
|
||||
alertDelay?: AlertDelay | null;
|
||||
flapping?: {
|
||||
lookBackWindow: number;
|
||||
statusChangeThreshold: number;
|
||||
};
|
||||
flapping?: Flapping | null;
|
||||
}
|
||||
|
||||
export type SanitizedRule<Params extends RuleTypeParams = never> = Omit<
|
||||
|
|
|
@ -17,10 +17,8 @@ const transformCreateRuleFlapping = (flapping: Rule['flapping']) => {
|
|||
}
|
||||
|
||||
return {
|
||||
flapping: {
|
||||
look_back_window: flapping.lookBackWindow,
|
||||
status_change_threshold: flapping.statusChangeThreshold,
|
||||
},
|
||||
look_back_window: flapping.lookBackWindow,
|
||||
status_change_threshold: flapping.statusChangeThreshold,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -60,5 +58,5 @@ export const transformCreateRuleBody: RewriteResponseCase<CreateRuleBody> = ({
|
|||
};
|
||||
}),
|
||||
...(alertDelay ? { alert_delay: alertDelay } : {}),
|
||||
...(flapping !== undefined ? transformCreateRuleFlapping(flapping) : {}),
|
||||
...(flapping !== undefined ? { flapping: transformCreateRuleFlapping(flapping) } : {}),
|
||||
});
|
||||
|
|
|
@ -17,10 +17,8 @@ const transformUpdateRuleFlapping = (flapping: Rule['flapping']) => {
|
|||
}
|
||||
|
||||
return {
|
||||
flapping: {
|
||||
look_back_window: flapping.lookBackWindow,
|
||||
status_change_threshold: flapping.statusChangeThreshold,
|
||||
},
|
||||
look_back_window: flapping.lookBackWindow,
|
||||
status_change_threshold: flapping.statusChangeThreshold,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -57,5 +55,5 @@ export const transformUpdateRuleBody: RewriteResponseCase<UpdateRuleBody> = ({
|
|||
};
|
||||
}),
|
||||
...(alertDelay ? { alert_delay: alertDelay } : {}),
|
||||
...(flapping !== undefined ? transformUpdateRuleFlapping(flapping) : {}),
|
||||
...(flapping !== undefined ? { flapping: transformUpdateRuleFlapping(flapping) } : {}),
|
||||
});
|
||||
|
|
|
@ -40,10 +40,8 @@ const transformFlapping = (flapping: AsApiContract<Rule['flapping']>) => {
|
|||
}
|
||||
|
||||
return {
|
||||
flapping: {
|
||||
lookBackWindow: flapping.look_back_window,
|
||||
statusChangeThreshold: flapping.status_change_threshold,
|
||||
},
|
||||
lookBackWindow: flapping.look_back_window,
|
||||
statusChangeThreshold: flapping.status_change_threshold,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -91,7 +89,7 @@ export const transformRule: RewriteRequestCase<Rule> = ({
|
|||
...(nextRun ? { nextRun } : {}),
|
||||
...(apiKeyCreatedByUser !== undefined ? { apiKeyCreatedByUser } : {}),
|
||||
...(alertDelay ? { alertDelay } : {}),
|
||||
...(flapping !== undefined ? transformFlapping(flapping) : {}),
|
||||
...(flapping !== undefined ? { flapping: transformFlapping(flapping) } : {}),
|
||||
...rest,
|
||||
});
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ import { TypeRegistry } from '../type_registry';
|
|||
|
||||
export type { SanitizedRuleAction as RuleAction } from '@kbn/alerting-types';
|
||||
|
||||
export type { Flapping } from '@kbn/alerting-types';
|
||||
|
||||
export type RuleTypeWithDescription = RuleType<string, string> & { description?: string };
|
||||
|
||||
export type RuleTypeIndexWithDescriptions = Map<string, RuleTypeWithDescription>;
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
MAX_LOOK_BACK_WINDOW,
|
||||
MIN_STATUS_CHANGE_THRESHOLD,
|
||||
MAX_STATUS_CHANGE_THRESHOLD,
|
||||
} from '@kbn/alerting-types';
|
||||
} from '@kbn/alerting-types/flapping/latest';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { RuleSettingsRangeInput } from './rule_settings_range_input';
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"action": "0e6fc0b74c7312a8c11ff6b14437b93a997358b8",
|
||||
"action_task_params": "b50cb5c8a493881474918e8d4985e61374ca4c30",
|
||||
"ad_hoc_run_params": "d4e3c5c794151d0a4f5c71e886b2aa638da73ad2",
|
||||
"alert": "e920b0583b1a32338d5dbbbfabb6ff2a511f5f6c",
|
||||
"alert": "05b07040b12ff45ab642f47464e8a6c903cf7b86",
|
||||
"api_key_pending_invalidation": "1399e87ca37b3d3a65d269c924eda70726cfe886",
|
||||
"apm-custom-dashboards": "b67128f78160c288bd7efe25b2da6e2afd5e82fc",
|
||||
"apm-indices": "8a2d68d415a4b542b26b0d292034a28ffac6fed4",
|
||||
|
|
|
@ -23,7 +23,7 @@ export type {
|
|||
RuleTaskState,
|
||||
RuleTaskParams,
|
||||
} from '@kbn/alerting-state-types';
|
||||
export type { AlertingFrameworkHealth } from '@kbn/alerting-types';
|
||||
export type { AlertingFrameworkHealth, Flapping } from '@kbn/alerting-types';
|
||||
export * from './alert_summary';
|
||||
export * from './builtin_action_groups';
|
||||
export * from './bulk_edit';
|
||||
|
|
|
@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation';
|
||||
import { notifyWhenSchemaV1, alertDelaySchemaV1 } from '../../../response';
|
||||
import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query';
|
||||
import { flappingSchemaV1 } from '../../../common';
|
||||
|
||||
export const actionFrequencySchema = schema.object({
|
||||
summary: schema.boolean({
|
||||
|
@ -186,6 +187,7 @@ export const createBodySchema = schema.object({
|
|||
actions: schema.arrayOf(actionSchema, { defaultValue: [] }),
|
||||
notify_when: schema.maybe(schema.nullable(notifyWhenSchemaV1)),
|
||||
alert_delay: schema.maybe(alertDelaySchemaV1),
|
||||
flapping: schema.maybe(schema.nullable(flappingSchemaV1)),
|
||||
});
|
||||
|
||||
export const createParamsSchema = schema.object({
|
||||
|
|
|
@ -31,6 +31,7 @@ export interface CreateRuleRequestBody<Params extends RuleParamsV1 = never> {
|
|||
actions: CreateBodySchema['actions'];
|
||||
notify_when?: CreateBodySchema['notify_when'];
|
||||
alert_delay?: CreateBodySchema['alert_delay'];
|
||||
flapping?: CreateBodySchema['flapping'];
|
||||
}
|
||||
|
||||
export interface CreateRuleResponse<Params extends RuleParamsV1 = never> {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation';
|
||||
import { notifyWhenSchemaV1, alertDelaySchemaV1 } from '../../../response';
|
||||
import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query';
|
||||
import { flappingSchemaV1 } from '../../../common';
|
||||
|
||||
export const actionFrequencySchema = schema.object({
|
||||
summary: schema.boolean({
|
||||
|
@ -158,6 +159,7 @@ export const updateBodySchema = schema.object({
|
|||
actions: schema.arrayOf(actionSchema, { defaultValue: [] }),
|
||||
notify_when: schema.maybe(schema.nullable(notifyWhenSchemaV1)),
|
||||
alert_delay: schema.maybe(alertDelaySchemaV1),
|
||||
flapping: schema.maybe(schema.nullable(flappingSchemaV1)),
|
||||
});
|
||||
|
||||
export const updateParamsSchema = schema.object({
|
||||
|
|
|
@ -30,6 +30,7 @@ export interface UpdateRuleRequestBody<Params extends RuleParamsV1 = never> {
|
|||
actions: UpdateBodySchema['actions'];
|
||||
notify_when?: UpdateBodySchema['notify_when'];
|
||||
alert_delay?: UpdateBodySchema['alert_delay'];
|
||||
flapping?: UpdateBodySchema['flapping'];
|
||||
}
|
||||
|
||||
export interface UpdateRuleResponse<Params extends RuleParamsV1 = never> {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './v1';
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import {
|
||||
MIN_LOOK_BACK_WINDOW as MIN_LOOK_BACK_WINDOW_V1,
|
||||
MAX_LOOK_BACK_WINDOW as MAX_LOOK_BACK_WINDOW_V1,
|
||||
MIN_STATUS_CHANGE_THRESHOLD as MIN_STATUS_CHANGE_THRESHOLD_V1,
|
||||
MAX_STATUS_CHANGE_THRESHOLD as MAX_STATUS_CHANGE_THRESHOLD_V1,
|
||||
} from '@kbn/alerting-types/flapping/v1';
|
||||
import { validateFlapping as validateFlappingV1 } from '../../../validation/validate_flapping/v1';
|
||||
|
||||
export const flappingSchema = schema.object(
|
||||
{
|
||||
look_back_window: schema.number({
|
||||
min: MIN_LOOK_BACK_WINDOW_V1,
|
||||
max: MAX_LOOK_BACK_WINDOW_V1,
|
||||
}),
|
||||
status_change_threshold: schema.number({
|
||||
min: MIN_STATUS_CHANGE_THRESHOLD_V1,
|
||||
max: MAX_STATUS_CHANGE_THRESHOLD_V1,
|
||||
}),
|
||||
},
|
||||
{ validate: validateFlappingV1 }
|
||||
);
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './v1';
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import { flappingSchemaV1 } from '../..';
|
||||
|
||||
export type Flapping = TypeOf<typeof flappingSchemaV1>;
|
|
@ -13,6 +13,8 @@ export {
|
|||
ruleExecutionStatusWarningReason,
|
||||
} from './constants/latest';
|
||||
|
||||
export { flappingSchema } from './flapping/schemas/latest';
|
||||
|
||||
export type {
|
||||
RuleNotifyWhen,
|
||||
RuleLastRunOutcomeValues,
|
||||
|
@ -21,6 +23,8 @@ export type {
|
|||
RuleExecutionStatusWarningReason,
|
||||
} from './constants/latest';
|
||||
|
||||
export type { Flapping } from './flapping/types/latest';
|
||||
|
||||
export {
|
||||
ruleNotifyWhen as ruleNotifyWhenV1,
|
||||
ruleLastRunOutcomeValues as ruleLastRunOutcomeValuesV1,
|
||||
|
@ -29,6 +33,8 @@ export {
|
|||
ruleExecutionStatusWarningReason as ruleExecutionStatusWarningReasonV1,
|
||||
} from './constants/v1';
|
||||
|
||||
export { flappingSchema as flappingSchemaV1 } from './flapping/schemas/v1';
|
||||
|
||||
export type {
|
||||
RuleNotifyWhen as RuleNotifyWhenV1,
|
||||
RuleLastRunOutcomeValues as RuleLastRunOutcomeValuesV1,
|
||||
|
@ -36,3 +42,5 @@ export type {
|
|||
RuleExecutionStatusErrorReason as RuleExecutionStatusErrorReasonV1,
|
||||
RuleExecutionStatusWarningReason as RuleExecutionStatusWarningReasonV1,
|
||||
} from './constants/v1';
|
||||
|
||||
export type { Flapping as FlappingV1 } from './flapping/types/v1';
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
ruleLastRunOutcomeValues as ruleLastRunOutcomeValuesV1,
|
||||
} from '../../common/constants/v1';
|
||||
import { validateNotifyWhenV1 } from '../../validation';
|
||||
import { flappingSchemaV1 } from '../../common';
|
||||
|
||||
export const ruleParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any()), {
|
||||
meta: { description: 'The parameters for the rule.' },
|
||||
|
@ -626,6 +627,7 @@ export const ruleResponseSchema = schema.object({
|
|||
)
|
||||
),
|
||||
alert_delay: schema.maybe(alertDelaySchema),
|
||||
flapping: schema.maybe(schema.nullable(flappingSchemaV1)),
|
||||
});
|
||||
|
||||
export const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string()));
|
||||
|
|
|
@ -54,4 +54,5 @@ export interface RuleResponse<Params extends RuleParams = never> {
|
|||
running?: RuleResponseSchemaType['running'];
|
||||
view_in_app_relative_url?: RuleResponseSchemaType['view_in_app_relative_url'];
|
||||
alert_delay?: RuleResponseSchemaType['alert_delay'];
|
||||
flapping?: RuleResponseSchemaType['flapping'];
|
||||
}
|
||||
|
|
|
@ -9,9 +9,11 @@ export { validateDuration } from './validate_duration/latest';
|
|||
export { validateHours } from './validate_hours/latest';
|
||||
export { validateTimezone } from './validate_timezone/latest';
|
||||
export { validateSnoozeSchedule } from './validate_snooze_schedule/latest';
|
||||
export { validateFlapping } from './validate_flapping/latest';
|
||||
|
||||
export { validateDuration as validateDurationV1 } from './validate_duration/v1';
|
||||
export { validateHours as validateHoursV1 } from './validate_hours/v1';
|
||||
export { validateNotifyWhen as validateNotifyWhenV1 } from './validate_notify_when/v1';
|
||||
export { validateTimezone as validateTimezoneV1 } from './validate_timezone/v1';
|
||||
export { validateSnoozeSchedule as validateSnoozeScheduleV1 } from './validate_snooze_schedule/v1';
|
||||
export { validateFlapping as validateFlappingV1 } from './validate_flapping/v1';
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './v1';
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { validateFlapping } from './v1';
|
||||
|
||||
describe('validateFlapping', () => {
|
||||
test('should error if look back window exceeds the lower bound', () => {
|
||||
const result = validateFlapping({
|
||||
look_back_window: 0,
|
||||
status_change_threshold: 10,
|
||||
});
|
||||
|
||||
expect(result).toEqual('look back window must be between 2 and 20');
|
||||
});
|
||||
|
||||
test('should error if look back window exceeds the upper bound', () => {
|
||||
const result = validateFlapping({
|
||||
look_back_window: 50,
|
||||
status_change_threshold: 10,
|
||||
});
|
||||
|
||||
expect(result).toEqual('look back window must be between 2 and 20');
|
||||
});
|
||||
|
||||
test('should error if status change threshold exceeds the lower bound', () => {
|
||||
const result = validateFlapping({
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 1,
|
||||
});
|
||||
|
||||
expect(result).toEqual('status change threshold must be between 2 and 20');
|
||||
});
|
||||
|
||||
test('should error if status change threshold exceeds the upper bound', () => {
|
||||
const result = validateFlapping({
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 50,
|
||||
});
|
||||
|
||||
expect(result).toEqual('status change threshold must be between 2 and 20');
|
||||
});
|
||||
|
||||
test('should error if status change threshold is greater than the look back window', () => {
|
||||
const result = validateFlapping({
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 15,
|
||||
});
|
||||
|
||||
expect(result).toEqual(
|
||||
'lookBackWindow (10) must be equal to or greater than statusChangeThreshold (15)'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 {
|
||||
MIN_LOOK_BACK_WINDOW as MIN_LOOK_BACK_WINDOW_V1,
|
||||
MAX_LOOK_BACK_WINDOW as MAX_LOOK_BACK_WINDOW_V1,
|
||||
MIN_STATUS_CHANGE_THRESHOLD as MIN_STATUS_CHANGE_THRESHOLD_V1,
|
||||
MAX_STATUS_CHANGE_THRESHOLD as MAX_STATUS_CHANGE_THRESHOLD_V1,
|
||||
} from '@kbn/alerting-types/flapping/v1';
|
||||
|
||||
export const validateFlapping = (flapping: {
|
||||
look_back_window: number;
|
||||
status_change_threshold: number;
|
||||
}) => {
|
||||
const { look_back_window: lookBackWindow, status_change_threshold: statusChangeThreshold } =
|
||||
flapping;
|
||||
|
||||
if (lookBackWindow < MIN_LOOK_BACK_WINDOW_V1 || lookBackWindow > MAX_LOOK_BACK_WINDOW_V1) {
|
||||
return `look back window must be between ${MIN_LOOK_BACK_WINDOW_V1} and ${MAX_LOOK_BACK_WINDOW_V1}`;
|
||||
}
|
||||
|
||||
if (
|
||||
statusChangeThreshold < MIN_STATUS_CHANGE_THRESHOLD_V1 ||
|
||||
statusChangeThreshold > MAX_STATUS_CHANGE_THRESHOLD_V1
|
||||
) {
|
||||
return `status change threshold must be between ${MIN_STATUS_CHANGE_THRESHOLD_V1} and ${MAX_STATUS_CHANGE_THRESHOLD_V1}`;
|
||||
}
|
||||
|
||||
if (lookBackWindow < statusChangeThreshold) {
|
||||
return `lookBackWindow (${lookBackWindow}) must be equal to or greater than statusChangeThreshold (${statusChangeThreshold})`;
|
||||
}
|
||||
};
|
|
@ -18,11 +18,6 @@ export interface RulesSettingsFlappingProperties {
|
|||
statusChangeThreshold: number;
|
||||
}
|
||||
|
||||
export interface RuleSpecificFlappingProperties {
|
||||
lookBackWindow: number;
|
||||
statusChangeThreshold: number;
|
||||
}
|
||||
|
||||
export type RulesSettingsFlapping = RulesSettingsFlappingProperties &
|
||||
RulesSettingsModificationMetadata;
|
||||
|
||||
|
@ -43,13 +38,6 @@ export interface RulesSettings {
|
|||
queryDelay?: RulesSettingsQueryDelay;
|
||||
}
|
||||
|
||||
export {
|
||||
MIN_LOOK_BACK_WINDOW,
|
||||
MAX_LOOK_BACK_WINDOW,
|
||||
MIN_STATUS_CHANGE_THRESHOLD,
|
||||
MAX_STATUS_CHANGE_THRESHOLD,
|
||||
} from '@kbn/alerting-types';
|
||||
|
||||
export const MIN_QUERY_DELAY = 0;
|
||||
export const MAX_QUERY_DELAY = 60;
|
||||
|
||||
|
|
|
@ -225,6 +225,18 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = {
|
|||
},
|
||||
},
|
||||
// NO NEED TO BE INDEXED
|
||||
// flapping: {
|
||||
// index: false,
|
||||
// properties: {
|
||||
// lookBackWindow: {
|
||||
// type: 'long',
|
||||
// },
|
||||
// statusChangeThreshold: {
|
||||
// type: 'long',
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// NO NEED TO BE INDEXED
|
||||
// nextRun: {
|
||||
// type: 'date',
|
||||
// },
|
||||
|
|
|
@ -3182,6 +3182,81 @@ describe('create()', () => {
|
|||
expect(taskManager.schedule).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should create rule with flapping', async () => {
|
||||
const flapping = {
|
||||
lookBackWindow: 10,
|
||||
statusChangeThreshold: 10,
|
||||
};
|
||||
|
||||
const data = getMockData({
|
||||
name: 'my rule name',
|
||||
flapping,
|
||||
});
|
||||
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
attributes: {
|
||||
enabled: false,
|
||||
name: ' my rule name ',
|
||||
alertTypeId: '123',
|
||||
schedule: { interval: 10000 },
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
executionStatus: getRuleExecutionStatusPending(now),
|
||||
running: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
actions: [],
|
||||
flapping,
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
id: '1',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = await rulesClient.create({ data, isFlappingEnabled: true });
|
||||
expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith(
|
||||
RULE_SAVED_OBJECT_TYPE,
|
||||
expect.objectContaining({
|
||||
flapping,
|
||||
}),
|
||||
{
|
||||
id: 'mock-saved-object-id',
|
||||
references: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
expect(result.flapping).toEqual(flapping);
|
||||
});
|
||||
|
||||
test('throws error when creating a rule with flapping if global flapping is disabled', async () => {
|
||||
const flapping = {
|
||||
lookBackWindow: 10,
|
||||
statusChangeThreshold: 10,
|
||||
};
|
||||
|
||||
const data = getMockData({
|
||||
name: 'my rule name',
|
||||
flapping,
|
||||
});
|
||||
|
||||
await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Error creating rule: can not create rule with flapping if global flapping is disabled"`
|
||||
);
|
||||
});
|
||||
|
||||
test('throws error when creating with an interval less than the minimum configured one when enforce = true', async () => {
|
||||
rulesClient = new RulesClient({
|
||||
...rulesClientParams,
|
||||
|
@ -3770,7 +3845,7 @@ describe('create()', () => {
|
|||
],
|
||||
});
|
||||
await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Failed to validate actions due to the following error: Action's alertsFilter must have either \\"query\\" or \\"timeframe\\" : 152"`
|
||||
`"Failed to validate actions due to the following error: Action's alertsFilter must have either \\"query\\" or \\"timeframe\\" : 154"`
|
||||
);
|
||||
expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled();
|
||||
expect(taskManager.schedule).not.toHaveBeenCalled();
|
||||
|
@ -3826,7 +3901,7 @@ describe('create()', () => {
|
|||
],
|
||||
});
|
||||
await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Failed to validate actions due to the following error: This ruleType (Test) can't have an action with Alerts Filter. Actions: [153]"`
|
||||
`"Failed to validate actions due to the following error: This ruleType (Test) can't have an action with Alerts Filter. Actions: [155]"`
|
||||
);
|
||||
expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled();
|
||||
expect(taskManager.schedule).not.toHaveBeenCalled();
|
||||
|
@ -3898,7 +3973,7 @@ describe('create()', () => {
|
|||
group: 'default',
|
||||
actionTypeId: 'test',
|
||||
params: { foo: true },
|
||||
uuid: '154',
|
||||
uuid: '156',
|
||||
},
|
||||
],
|
||||
alertTypeId: '123',
|
||||
|
@ -4127,13 +4202,13 @@ describe('create()', () => {
|
|||
params: {
|
||||
foo: true,
|
||||
},
|
||||
uuid: '156',
|
||||
uuid: '158',
|
||||
},
|
||||
{
|
||||
actionRef: 'system_action:system_action-id',
|
||||
actionTypeId: '.test',
|
||||
params: { foo: 'test' },
|
||||
uuid: '157',
|
||||
uuid: '159',
|
||||
},
|
||||
],
|
||||
alertTypeId: '123',
|
||||
|
@ -4205,13 +4280,13 @@ describe('create()', () => {
|
|||
params: {
|
||||
foo: true,
|
||||
},
|
||||
uuid: '158',
|
||||
uuid: '160',
|
||||
},
|
||||
{
|
||||
actionRef: 'system_action:system_action-id',
|
||||
actionTypeId: '.test',
|
||||
params: { foo: 'test' },
|
||||
uuid: '159',
|
||||
uuid: '161',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -48,6 +48,7 @@ export interface CreateRuleParams<Params extends RuleParams = never> {
|
|||
data: CreateRuleData<Params>;
|
||||
options?: CreateRuleOptions;
|
||||
allowMissingConnectorSecrets?: boolean;
|
||||
isFlappingEnabled?: boolean;
|
||||
}
|
||||
|
||||
export async function createRule<Params extends RuleParams = never>(
|
||||
|
@ -55,7 +56,13 @@ export async function createRule<Params extends RuleParams = never>(
|
|||
createParams: CreateRuleParams<Params>
|
||||
// TODO (http-versioning): This should be of type Rule, change this when all rule types are fixed
|
||||
): Promise<SanitizedRule<Params>> {
|
||||
const { data: initialData, options, allowMissingConnectorSecrets } = createParams;
|
||||
const {
|
||||
data: initialData,
|
||||
options,
|
||||
allowMissingConnectorSecrets,
|
||||
isFlappingEnabled = false,
|
||||
} = createParams;
|
||||
|
||||
const actionsClient = await context.getActionsClient();
|
||||
|
||||
const { actions: genAction, systemActions: genSystemActions } = await addGeneratedActionValues(
|
||||
|
@ -170,6 +177,12 @@ export async function createRule<Params extends RuleParams = never>(
|
|||
);
|
||||
}
|
||||
|
||||
if (initialData.flapping !== undefined && !isFlappingEnabled) {
|
||||
throw Boom.badRequest(
|
||||
'Error creating rule: can not create rule with flapping if global flapping is disabled'
|
||||
);
|
||||
}
|
||||
|
||||
const allActions = [...data.actions, ...(data.systemActions ?? [])];
|
||||
// Extract saved object references for this rule
|
||||
const {
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
alertDelaySchema,
|
||||
actionRequestSchema,
|
||||
systemActionRequestSchema,
|
||||
flappingSchema,
|
||||
} from '../../../schemas';
|
||||
|
||||
export const createRuleDataSchema = schema.object(
|
||||
|
@ -36,6 +37,7 @@ export const createRuleDataSchema = schema.object(
|
|||
),
|
||||
notifyWhen: schema.maybe(schema.nullable(notifyWhenSchema)),
|
||||
alertDelay: schema.maybe(alertDelaySchema),
|
||||
flapping: schema.maybe(schema.nullable(flappingSchema)),
|
||||
},
|
||||
{ unknowns: 'allow' }
|
||||
);
|
||||
|
|
|
@ -24,4 +24,5 @@ export interface CreateRuleData<Params extends RuleParams = never> {
|
|||
systemActions?: CreateRuleDataType['systemActions'];
|
||||
notifyWhen?: CreateRuleDataType['notifyWhen'];
|
||||
alertDelay?: CreateRuleDataType['alertDelay'];
|
||||
flapping?: CreateRuleDataType['flapping'];
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
alertDelaySchema,
|
||||
actionRequestSchema,
|
||||
systemActionRequestSchema,
|
||||
flappingSchema,
|
||||
} from '../../../schemas';
|
||||
|
||||
export const updateRuleDataSchema = schema.object(
|
||||
|
@ -27,6 +28,7 @@ export const updateRuleDataSchema = schema.object(
|
|||
systemActions: schema.maybe(schema.arrayOf(systemActionRequestSchema, { defaultValue: [] })),
|
||||
notifyWhen: schema.maybe(schema.nullable(notifyWhenSchema)),
|
||||
alertDelay: schema.maybe(alertDelaySchema),
|
||||
flapping: schema.maybe(schema.nullable(flappingSchema)),
|
||||
},
|
||||
{ unknowns: 'allow' }
|
||||
);
|
||||
|
|
|
@ -21,4 +21,5 @@ export interface UpdateRuleData<Params extends RuleParams = never> {
|
|||
systemActions?: UpdateRuleDataType['systemActions'];
|
||||
notifyWhen?: UpdateRuleDataType['notifyWhen'];
|
||||
alertDelay?: UpdateRuleDataType['alertDelay'];
|
||||
flapping?: UpdateRuleDataType['flapping'];
|
||||
}
|
||||
|
|
|
@ -1769,6 +1769,100 @@ describe('update()', () => {
|
|||
expect(rulesClientParams.createAPIKey).toHaveBeenCalledWith('Alerting: myType/my alert name');
|
||||
});
|
||||
|
||||
it('should update rule flapping', async () => {
|
||||
const flapping = {
|
||||
lookBackWindow: 10,
|
||||
statusChangeThreshold: 10,
|
||||
};
|
||||
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
attributes: {
|
||||
enabled: true,
|
||||
schedule: { interval: '1m' },
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
actions: [],
|
||||
notifyWhen: 'onActiveAlert',
|
||||
revision: 1,
|
||||
scheduledTaskId: 'task-123',
|
||||
executionStatus: {
|
||||
lastExecutionDate: '2019-02-12T21:01:22.479Z',
|
||||
status: 'pending',
|
||||
},
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
flapping,
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
|
||||
const result = await rulesClient.update({
|
||||
id: '1',
|
||||
data: {
|
||||
schedule: { interval: '1m' },
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
throttle: null,
|
||||
notifyWhen: 'onActiveAlert',
|
||||
actions: [],
|
||||
systemActions: [],
|
||||
flapping,
|
||||
},
|
||||
isFlappingEnabled: true,
|
||||
});
|
||||
|
||||
expect(unsecuredSavedObjectsClient.create).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
RULE_SAVED_OBJECT_TYPE,
|
||||
expect.objectContaining({
|
||||
flapping,
|
||||
}),
|
||||
{
|
||||
id: '1',
|
||||
overwrite: true,
|
||||
references: [],
|
||||
version: '123',
|
||||
}
|
||||
);
|
||||
|
||||
expect(result.flapping).toEqual(flapping);
|
||||
});
|
||||
|
||||
it('should throw error when updating a rule with flapping if global flapping is disabled', async () => {
|
||||
const flapping = {
|
||||
lookBackWindow: 10,
|
||||
statusChangeThreshold: 10,
|
||||
};
|
||||
|
||||
await expect(
|
||||
rulesClient.update({
|
||||
id: '1',
|
||||
data: {
|
||||
schedule: { interval: '1m' },
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
throttle: null,
|
||||
notifyWhen: 'onActiveAlert',
|
||||
actions: [],
|
||||
systemActions: [],
|
||||
flapping,
|
||||
},
|
||||
isFlappingEnabled: false,
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Error updating rule: can not update rule flapping if global flapping is disabled"`
|
||||
);
|
||||
});
|
||||
|
||||
it('swallows error when invalidate API key throws', async () => {
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
|
|
|
@ -41,6 +41,36 @@ import { RuleAttributes } from '../../../../data/rule/types';
|
|||
import { transformRuleAttributesToRuleDomain, transformRuleDomainToRule } from '../../transforms';
|
||||
import { ruleDomainSchema } from '../../schemas';
|
||||
|
||||
const validateCanUpdateFlapping = (
|
||||
isFlappingEnabled: boolean,
|
||||
originalFlapping: RuleAttributes['flapping'],
|
||||
updateFlapping: UpdateRuleParams['data']['flapping']
|
||||
) => {
|
||||
// If flapping is enabled, allow rule flapping to be updated and do nothing
|
||||
if (isFlappingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If updated flapping is undefined then don't do anything, it's not being updated
|
||||
if (updateFlapping === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If both versions are falsy, allow it even if its changing between undefined and null
|
||||
if (!originalFlapping && !updateFlapping) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If both values are equal, allow it because it's essentially not changing anything
|
||||
if (isEqual(originalFlapping, updateFlapping)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw Boom.badRequest(
|
||||
`Error updating rule: can not update rule flapping if global flapping is disabled`
|
||||
);
|
||||
};
|
||||
|
||||
type ShouldIncrementRevision = (params?: RuleParams) => boolean;
|
||||
|
||||
export interface UpdateRuleParams<Params extends RuleParams = never> {
|
||||
|
@ -48,6 +78,7 @@ export interface UpdateRuleParams<Params extends RuleParams = never> {
|
|||
data: UpdateRuleData<Params>;
|
||||
allowMissingConnectorSecrets?: boolean;
|
||||
shouldIncrementRevision?: ShouldIncrementRevision;
|
||||
isFlappingEnabled?: boolean;
|
||||
}
|
||||
|
||||
export async function updateRule<Params extends RuleParams = never>(
|
||||
|
@ -70,6 +101,7 @@ async function updateWithOCC<Params extends RuleParams = never>(
|
|||
data: initialData,
|
||||
allowMissingConnectorSecrets,
|
||||
id,
|
||||
isFlappingEnabled = false,
|
||||
shouldIncrementRevision = () => true,
|
||||
} = updateParams;
|
||||
|
||||
|
@ -114,8 +146,18 @@ async function updateWithOCC<Params extends RuleParams = never>(
|
|||
systemActions: genSystemActions,
|
||||
};
|
||||
|
||||
const { alertTypeId, consumer, enabled, schedule, name, apiKey, apiKeyCreatedByUser } =
|
||||
originalRuleSavedObject.attributes;
|
||||
const {
|
||||
alertTypeId,
|
||||
consumer,
|
||||
enabled,
|
||||
schedule,
|
||||
name,
|
||||
apiKey,
|
||||
apiKeyCreatedByUser,
|
||||
flapping: originalFlapping,
|
||||
} = originalRuleSavedObject.attributes;
|
||||
|
||||
validateCanUpdateFlapping(isFlappingEnabled, originalFlapping, initialData.flapping);
|
||||
|
||||
let validationPayload: ValidateScheduleLimitResult = null;
|
||||
if (enabled && schedule.interval !== data.schedule.interval) {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
export const flappingSchema = schema.object({
|
||||
lookBackWindow: schema.number(),
|
||||
statusChangeThreshold: schema.number(),
|
||||
});
|
|
@ -8,3 +8,4 @@
|
|||
export * from './rule_schemas';
|
||||
export * from './action_schemas';
|
||||
export * from './notify_when_schema';
|
||||
export * from './flapping_schema';
|
||||
|
|
|
@ -16,6 +16,7 @@ import { rRuleSchema } from '../../r_rule/schemas';
|
|||
import { dateSchema } from './date_schema';
|
||||
import { notifyWhenSchema } from './notify_when_schema';
|
||||
import { actionSchema, systemActionSchema } from './action_schemas';
|
||||
import { flappingSchema } from './flapping_schema';
|
||||
|
||||
export const ruleParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any()));
|
||||
export const mappedParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any()));
|
||||
|
@ -177,6 +178,7 @@ export const ruleDomainSchema = schema.object({
|
|||
viewInAppRelativeUrl: schema.maybe(schema.nullable(schema.string())),
|
||||
alertDelay: schema.maybe(alertDelaySchema),
|
||||
legacyId: schema.maybe(schema.nullable(schema.string())),
|
||||
flapping: schema.maybe(schema.nullable(flappingSchema)),
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -217,4 +219,5 @@ export const ruleSchema = schema.object({
|
|||
viewInAppRelativeUrl: schema.maybe(schema.nullable(schema.string())),
|
||||
alertDelay: schema.maybe(alertDelaySchema),
|
||||
legacyId: schema.maybe(schema.nullable(schema.string())),
|
||||
flapping: schema.maybe(schema.nullable(flappingSchema)),
|
||||
});
|
||||
|
|
|
@ -92,6 +92,10 @@ describe('transformRuleAttributesToRuleDomain', () => {
|
|||
updatedBy: 'user',
|
||||
apiKey: MOCK_API_KEY,
|
||||
apiKeyOwner: 'user',
|
||||
flapping: {
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 20,
|
||||
},
|
||||
};
|
||||
|
||||
it('transforms the actions correctly', () => {
|
||||
|
@ -106,6 +110,13 @@ describe('transformRuleAttributesToRuleDomain', () => {
|
|||
isSystemAction
|
||||
);
|
||||
|
||||
expect(res.flapping).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"lookBackWindow": 20,
|
||||
"statusChangeThreshold": 20,
|
||||
}
|
||||
`);
|
||||
|
||||
expect(res.actions).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
|
|
|
@ -232,6 +232,7 @@ export const transformRuleAttributesToRuleDomain = <Params extends RuleParams =
|
|||
running: esRule.running,
|
||||
...(esRule.alertDelay ? { alertDelay: esRule.alertDelay } : {}),
|
||||
...(esRule.legacyId !== undefined ? { legacyId: esRule.legacyId } : {}),
|
||||
...(esRule.flapping !== undefined ? { flapping: esRule.flapping } : {}),
|
||||
};
|
||||
|
||||
// Bad casts, but will fix once we fix all rule types
|
||||
|
|
|
@ -67,6 +67,10 @@ describe('transformRuleDomainToRule', () => {
|
|||
updatedBy: 'user',
|
||||
apiKey: MOCK_API_KEY,
|
||||
apiKeyOwner: 'user',
|
||||
flapping: {
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 20,
|
||||
},
|
||||
};
|
||||
|
||||
it('should transform rule domain to rule', () => {
|
||||
|
@ -100,6 +104,10 @@ describe('transformRuleDomainToRule', () => {
|
|||
revision: 0,
|
||||
updatedBy: 'user',
|
||||
apiKeyOwner: 'user',
|
||||
flapping: {
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 20,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -135,6 +143,10 @@ describe('transformRuleDomainToRule', () => {
|
|||
revision: 0,
|
||||
updatedBy: 'user',
|
||||
apiKeyOwner: 'user',
|
||||
flapping: {
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 20,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -172,6 +184,10 @@ describe('transformRuleDomainToRule', () => {
|
|||
revision: 0,
|
||||
updatedBy: 'user',
|
||||
apiKeyOwner: 'user',
|
||||
flapping: {
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 20,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -53,6 +53,7 @@ export const transformRuleDomainToRule = <Params extends RuleParams = never>(
|
|||
viewInAppRelativeUrl: ruleDomain.viewInAppRelativeUrl,
|
||||
alertDelay: ruleDomain.alertDelay,
|
||||
legacyId: ruleDomain.legacyId,
|
||||
flapping: ruleDomain.flapping,
|
||||
};
|
||||
|
||||
if (isPublic) {
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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 { RuleDomain } from '../types';
|
||||
import { transformRuleDomainToRuleAttributes } from './transform_rule_domain_to_rule_attributes';
|
||||
|
||||
describe('transformRuleDomainToRuleAttributes', () => {
|
||||
const MOCK_API_KEY = Buffer.from('123:abc').toString('base64');
|
||||
|
||||
const defaultAction = {
|
||||
id: '1',
|
||||
uuid: 'test-uuid',
|
||||
group: 'default',
|
||||
actionTypeId: 'test',
|
||||
params: {},
|
||||
};
|
||||
|
||||
const rule: RuleDomain<{}> = {
|
||||
id: 'test',
|
||||
enabled: false,
|
||||
name: 'my rule name',
|
||||
tags: ['foo'],
|
||||
alertTypeId: 'myType',
|
||||
consumer: 'myApp',
|
||||
schedule: { interval: '1m' },
|
||||
actions: [defaultAction],
|
||||
params: {},
|
||||
mapped_params: {},
|
||||
createdBy: 'user',
|
||||
createdAt: new Date('2019-02-12T21:01:22.479Z'),
|
||||
updatedAt: new Date('2019-02-12T21:01:22.479Z'),
|
||||
legacyId: 'legacyId',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
snoozeSchedule: [],
|
||||
scheduledTaskId: 'task-123',
|
||||
executionStatus: {
|
||||
lastExecutionDate: new Date('2019-02-12T21:01:22.479Z'),
|
||||
status: 'pending' as const,
|
||||
},
|
||||
throttle: null,
|
||||
notifyWhen: null,
|
||||
revision: 0,
|
||||
updatedBy: 'user',
|
||||
apiKey: MOCK_API_KEY,
|
||||
apiKeyOwner: 'user',
|
||||
flapping: {
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 20,
|
||||
},
|
||||
};
|
||||
|
||||
test('should transform rule domain to rule attribute', () => {
|
||||
const result = transformRuleDomainToRuleAttributes({
|
||||
rule,
|
||||
actionsWithRefs: [
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
uuid: 'test-uuid',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
params: {
|
||||
legacyId: 'test',
|
||||
paramsWithRefs: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"actionRef": "action_0",
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"params": Object {},
|
||||
"uuid": "test-uuid",
|
||||
},
|
||||
],
|
||||
"alertTypeId": "myType",
|
||||
"apiKey": "MTIzOmFiYw==",
|
||||
"apiKeyOwner": "user",
|
||||
"consumer": "myApp",
|
||||
"createdAt": "2019-02-12T21:01:22.479Z",
|
||||
"createdBy": "user",
|
||||
"enabled": false,
|
||||
"executionStatus": Object {
|
||||
"lastExecutionDate": "2019-02-12T21:01:22.479Z",
|
||||
"status": "pending",
|
||||
},
|
||||
"flapping": Object {
|
||||
"lookBackWindow": 20,
|
||||
"statusChangeThreshold": 20,
|
||||
},
|
||||
"legacyId": "test",
|
||||
"muteAll": false,
|
||||
"mutedInstanceIds": Array [],
|
||||
"name": "my rule name",
|
||||
"notifyWhen": null,
|
||||
"params": Object {},
|
||||
"revision": 0,
|
||||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"scheduledTaskId": "task-123",
|
||||
"snoozeSchedule": Array [],
|
||||
"tags": Array [
|
||||
"foo",
|
||||
],
|
||||
"throttle": null,
|
||||
"updatedAt": "2019-02-12T21:01:22.479Z",
|
||||
"updatedBy": "user",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -80,5 +80,6 @@ export const transformRuleDomainToRuleAttributes = ({
|
|||
revision: rule.revision,
|
||||
...(rule.running !== undefined ? { running: rule.running } : {}),
|
||||
...(rule.alertDelay !== undefined ? { alertDelay: rule.alertDelay } : {}),
|
||||
...(rule.flapping !== undefined ? { flapping: rule.flapping } : {}),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -84,6 +84,7 @@ export interface Rule<Params extends RuleParams = never> {
|
|||
viewInAppRelativeUrl?: RuleSchemaType['viewInAppRelativeUrl'];
|
||||
alertDelay?: RuleSchemaType['alertDelay'];
|
||||
legacyId?: RuleSchemaType['legacyId'];
|
||||
flapping?: RuleSchemaType['flapping'];
|
||||
}
|
||||
|
||||
export interface RuleDomain<Params extends RuleParams = never> {
|
||||
|
@ -122,4 +123,5 @@ export interface RuleDomain<Params extends RuleParams = never> {
|
|||
viewInAppRelativeUrl?: RuleDomainSchemaType['viewInAppRelativeUrl'];
|
||||
alertDelay?: RuleSchemaType['alertDelay'];
|
||||
legacyId?: RuleSchemaType['legacyId'];
|
||||
flapping?: RuleSchemaType['flapping'];
|
||||
}
|
||||
|
|
|
@ -147,6 +147,11 @@ interface AlertDelayAttributes {
|
|||
active: number;
|
||||
}
|
||||
|
||||
interface FlappingAttributes {
|
||||
lookBackWindow: number;
|
||||
statusChangeThreshold: number;
|
||||
}
|
||||
|
||||
export interface RuleAttributes {
|
||||
name: string;
|
||||
tags: string[];
|
||||
|
@ -180,4 +185,5 @@ export interface RuleAttributes {
|
|||
revision: number;
|
||||
running?: boolean | null;
|
||||
alertDelay?: AlertDelayAttributes;
|
||||
flapping?: FlappingAttributes | null;
|
||||
}
|
||||
|
|
|
@ -676,7 +676,10 @@ export class AlertingPlugin {
|
|||
getRulesClient: () => {
|
||||
return rulesClientFactory!.create(request, savedObjects);
|
||||
},
|
||||
getRulesSettingsClient: () => {
|
||||
getRulesSettingsClient: (withoutAuth?: boolean) => {
|
||||
if (withoutAuth) {
|
||||
return rulesSettingsClientFactory.create(request);
|
||||
}
|
||||
return rulesSettingsClientFactory.createWithAuthorization(request);
|
||||
},
|
||||
getMaintenanceWindowClient: () => {
|
||||
|
|
|
@ -227,6 +227,7 @@ describe('createRuleRoute', () => {
|
|||
],
|
||||
"throttle": "30s",
|
||||
},
|
||||
"isFlappingEnabled": true,
|
||||
"options": Object {
|
||||
"id": undefined,
|
||||
},
|
||||
|
@ -343,6 +344,7 @@ describe('createRuleRoute', () => {
|
|||
],
|
||||
"throttle": "30s",
|
||||
},
|
||||
"isFlappingEnabled": true,
|
||||
"options": Object {
|
||||
"id": "custom-id",
|
||||
},
|
||||
|
@ -460,6 +462,7 @@ describe('createRuleRoute', () => {
|
|||
],
|
||||
"throttle": "30s",
|
||||
},
|
||||
"isFlappingEnabled": true,
|
||||
"options": Object {
|
||||
"id": "custom-id",
|
||||
},
|
||||
|
@ -577,6 +580,7 @@ describe('createRuleRoute', () => {
|
|||
],
|
||||
"throttle": "30s",
|
||||
},
|
||||
"isFlappingEnabled": true,
|
||||
"options": Object {
|
||||
"id": "custom-id",
|
||||
},
|
||||
|
@ -732,6 +736,7 @@ describe('createRuleRoute', () => {
|
|||
],
|
||||
"throttle": "30s",
|
||||
},
|
||||
"isFlappingEnabled": true,
|
||||
"options": Object {
|
||||
"id": undefined,
|
||||
},
|
||||
|
|
|
@ -64,6 +64,7 @@ export const createRuleRoute = ({ router, licenseState, usageCounter }: RouteOpt
|
|||
verifyAccessAndContext(licenseState, async function (context, req, res) {
|
||||
const rulesClient = (await context.alerting).getRulesClient();
|
||||
const actionsClient = (await context.actions).getActionsClient();
|
||||
const rulesSettingsClient = (await context.alerting).getRulesSettingsClient(true);
|
||||
|
||||
// Assert versioned inputs
|
||||
const createRuleData: CreateRuleRequestBodyV1<RuleParamsV1> = req.body;
|
||||
|
@ -90,6 +91,8 @@ export const createRuleRoute = ({ router, licenseState, usageCounter }: RouteOpt
|
|||
actionsClient.isSystemAction(action.id)
|
||||
);
|
||||
|
||||
const flappingSettings = await rulesSettingsClient.flapping().get();
|
||||
|
||||
// TODO (http-versioning): Remove this cast, this enables us to move forward
|
||||
// without fixing all of other solution types
|
||||
const createdRule: Rule<RuleParamsV1> = (await rulesClient.create<RuleParamsV1>({
|
||||
|
@ -98,6 +101,7 @@ export const createRuleRoute = ({ router, licenseState, usageCounter }: RouteOpt
|
|||
actions,
|
||||
systemActions,
|
||||
}),
|
||||
isFlappingEnabled: flappingSettings.enabled,
|
||||
options: { id: params?.id },
|
||||
})) as Rule<RuleParamsV1>;
|
||||
|
||||
|
|
|
@ -66,6 +66,18 @@ const transformCreateBodySystemActions = (actions: CreateRuleActionV1[]): System
|
|||
});
|
||||
};
|
||||
|
||||
const transformCreateBodyFlapping = <Params extends RuleParams = never>(
|
||||
flapping: CreateRuleRequestBodyV1<Params>['flapping']
|
||||
) => {
|
||||
if (!flapping) {
|
||||
return flapping;
|
||||
}
|
||||
return {
|
||||
lookBackWindow: flapping.look_back_window,
|
||||
statusChangeThreshold: flapping.status_change_threshold,
|
||||
};
|
||||
};
|
||||
|
||||
export const transformCreateBody = <Params extends RuleParams = never>({
|
||||
createBody,
|
||||
actions,
|
||||
|
@ -88,5 +100,8 @@ export const transformCreateBody = <Params extends RuleParams = never>({
|
|||
systemActions: transformCreateBodySystemActions(systemActions),
|
||||
...(createBody.notify_when ? { notifyWhen: createBody.notify_when } : {}),
|
||||
...(createBody.alert_delay ? { alertDelay: createBody.alert_delay } : {}),
|
||||
...(createBody.flapping !== undefined
|
||||
? { flapping: transformCreateBodyFlapping(createBody.flapping) }
|
||||
: {}),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
transformRuleActionsV1,
|
||||
transformMonitoringV1,
|
||||
transformRuleLastRunV1,
|
||||
transformFlappingV1,
|
||||
} from '../../../../transforms';
|
||||
|
||||
export const transformPartialRule = <Params extends RuleParams = never>(
|
||||
|
@ -78,6 +79,7 @@ export const transformPartialRule = <Params extends RuleParams = never>(
|
|||
? { view_in_app_relative_url: rule.viewInAppRelativeUrl }
|
||||
: {}),
|
||||
...(rule.alertDelay !== undefined ? { alert_delay: rule.alertDelay } : {}),
|
||||
...(rule.flapping !== undefined ? { flapping: transformFlappingV1(rule.flapping) } : {}),
|
||||
};
|
||||
|
||||
type RuleKeys = keyof RuleResponseV1<RuleParamsV1>;
|
||||
|
|
|
@ -70,6 +70,18 @@ export const transformUpdateBodySystemActions = (
|
|||
});
|
||||
};
|
||||
|
||||
const transformUpdateBodyFlapping = <Params extends RuleParams = never>(
|
||||
flapping: UpdateRuleRequestBodyV1<Params>['flapping']
|
||||
) => {
|
||||
if (!flapping) {
|
||||
return flapping;
|
||||
}
|
||||
return {
|
||||
lookBackWindow: flapping.look_back_window,
|
||||
statusChangeThreshold: flapping.status_change_threshold,
|
||||
};
|
||||
};
|
||||
|
||||
export const transformUpdateBody = <Params extends RuleParams = never>({
|
||||
updateBody,
|
||||
actions,
|
||||
|
@ -89,5 +101,8 @@ export const transformUpdateBody = <Params extends RuleParams = never>({
|
|||
systemActions: transformUpdateBodySystemActions(systemActions),
|
||||
...(updateBody.notify_when ? { notifyWhen: updateBody.notify_when } : {}),
|
||||
...(updateBody.alert_delay ? { alertDelay: updateBody.alert_delay } : {}),
|
||||
...(updateBody.flapping !== undefined
|
||||
? { flapping: transformUpdateBodyFlapping(updateBody.flapping) }
|
||||
: {}),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -215,6 +215,7 @@ describe('updateRuleRoute', () => {
|
|||
"throttle": "10m",
|
||||
},
|
||||
"id": "1",
|
||||
"isFlappingEnabled": true,
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
|
|
@ -66,6 +66,7 @@ export const updateRuleRoute = (
|
|||
verifyAccessAndContext(licenseState, async function (context, req, res) {
|
||||
const rulesClient = (await context.alerting).getRulesClient();
|
||||
const actionsClient = (await context.actions).getActionsClient();
|
||||
const rulesSettingsClient = (await context.alerting).getRulesSettingsClient(true);
|
||||
|
||||
// Assert versioned inputs
|
||||
const updateRuleData: UpdateRuleRequestBodyV1<RuleParamsV1> = req.body;
|
||||
|
@ -86,6 +87,8 @@ export const updateRuleRoute = (
|
|||
actionsClient.isSystemAction(action.id)
|
||||
);
|
||||
|
||||
const flappingSettings = await rulesSettingsClient.flapping().get();
|
||||
|
||||
// TODO (http-versioning): Remove this cast, this enables us to move forward
|
||||
// without fixing all of other solution types
|
||||
const updatedRule: Rule<RuleParamsV1> = (await rulesClient.update<RuleParamsV1>({
|
||||
|
@ -95,6 +98,7 @@ export const updateRuleRoute = (
|
|||
actions,
|
||||
systemActions,
|
||||
}),
|
||||
isFlappingEnabled: flappingSettings.enabled,
|
||||
})) as Rule<RuleParamsV1>;
|
||||
|
||||
// Assert versioned response type
|
||||
|
|
|
@ -10,10 +10,12 @@ export {
|
|||
transformRuleActions,
|
||||
transformRuleLastRun,
|
||||
transformMonitoring,
|
||||
transformFlapping,
|
||||
} from './transform_rule_to_rule_response/latest';
|
||||
export {
|
||||
transformRuleToRuleResponse as transformRuleToRuleResponseV1,
|
||||
transformRuleActions as transformRuleActionsV1,
|
||||
transformRuleLastRun as transformRuleLastRunV1,
|
||||
transformMonitoring as transformMonitoringV1,
|
||||
transformFlapping as transformFlappingV1,
|
||||
} from './transform_rule_to_rule_response/v1';
|
||||
|
|
|
@ -88,6 +88,17 @@ export const transformRuleActions = (
|
|||
];
|
||||
};
|
||||
|
||||
export const transformFlapping = (flapping: Rule['flapping']) => {
|
||||
if (!flapping) {
|
||||
return flapping;
|
||||
}
|
||||
|
||||
return {
|
||||
look_back_window: flapping.lookBackWindow,
|
||||
status_change_threshold: flapping.statusChangeThreshold,
|
||||
};
|
||||
};
|
||||
|
||||
export const transformRuleToRuleResponse = <Params extends RuleParams = never>(
|
||||
rule: Rule<Params>
|
||||
): RuleResponseV1<RuleParamsV1> => ({
|
||||
|
@ -144,4 +155,5 @@ export const transformRuleToRuleResponse = <Params extends RuleParams = never>(
|
|||
? { view_in_app_relative_url: rule.viewInAppRelativeUrl }
|
||||
: {}),
|
||||
...(rule.alertDelay !== undefined ? { alert_delay: rule.alertDelay } : {}),
|
||||
...(rule.flapping !== undefined ? { flapping: transformFlapping(rule.flapping) } : {}),
|
||||
});
|
||||
|
|
|
@ -122,7 +122,7 @@ function getPartialRuleFromRaw<Params extends RuleTypeParams>(
|
|||
actions,
|
||||
snoozeSchedule,
|
||||
lastRun,
|
||||
isSnoozedUntil: DoNotUseIsSNoozedUntil,
|
||||
isSnoozedUntil: DoNotUseIsSnoozedUntil,
|
||||
...partialRawRule
|
||||
} = rawRule;
|
||||
|
||||
|
|
|
@ -12,15 +12,17 @@ import {
|
|||
SavedObject,
|
||||
SavedObjectsErrorHelpers,
|
||||
} from '@kbn/core/server';
|
||||
import {
|
||||
MAX_LOOK_BACK_WINDOW,
|
||||
MAX_STATUS_CHANGE_THRESHOLD,
|
||||
MIN_LOOK_BACK_WINDOW,
|
||||
MIN_STATUS_CHANGE_THRESHOLD,
|
||||
} from '@kbn/alerting-types/flapping/latest';
|
||||
import {
|
||||
RulesSettings,
|
||||
RulesSettingsFlapping,
|
||||
RulesSettingsFlappingProperties,
|
||||
RulesSettingsModificationMetadata,
|
||||
MIN_LOOK_BACK_WINDOW,
|
||||
MAX_LOOK_BACK_WINDOW,
|
||||
MIN_STATUS_CHANGE_THRESHOLD,
|
||||
MAX_STATUS_CHANGE_THRESHOLD,
|
||||
RULES_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
RULES_SETTINGS_FLAPPING_SAVED_OBJECT_ID,
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
RulesSettingsQueryDelayClientApi,
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
DEFAULT_QUERY_DELAY_SETTINGS,
|
||||
RulesSettingsFlappingProperties,
|
||||
} from '../types';
|
||||
|
||||
export type RulesSettingsClientMock = jest.Mocked<RulesSettingsClientApi>;
|
||||
|
@ -19,9 +20,9 @@ export type RulesSettingsQueryDelayClientMock = jest.Mocked<RulesSettingsQueryDe
|
|||
|
||||
// Warning: Becareful when resetting all mocks in tests as it would clear
|
||||
// the mock return value on the flapping
|
||||
const createRulesSettingsClientMock = () => {
|
||||
const createRulesSettingsClientMock = (flappingOverride?: RulesSettingsFlappingProperties) => {
|
||||
const flappingMocked: RulesSettingsFlappingClientMock = {
|
||||
get: jest.fn().mockReturnValue(DEFAULT_FLAPPING_SETTINGS),
|
||||
get: jest.fn().mockReturnValue(flappingOverride || DEFAULT_FLAPPING_SETTINGS),
|
||||
update: jest.fn(),
|
||||
};
|
||||
const queryDelayMocked: RulesSettingsQueryDelayClientMock = {
|
||||
|
@ -36,7 +37,8 @@ const createRulesSettingsClientMock = () => {
|
|||
};
|
||||
|
||||
export const rulesSettingsClientMock: {
|
||||
create: () => RulesSettingsClientMock;
|
||||
create: (flappingOverride?: RulesSettingsFlappingProperties) => RulesSettingsClientMock;
|
||||
} = {
|
||||
create: createRulesSettingsClientMock,
|
||||
create: (flappingOverride?: RulesSettingsFlappingProperties) =>
|
||||
createRulesSettingsClientMock(flappingOverride),
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server';
|
||||
import { rawRuleSchemaV1 } from '../schemas/raw_rule';
|
||||
import { rawRuleSchemaV1, rawRuleSchemaV2 } from '../schemas/raw_rule';
|
||||
|
||||
export const ruleModelVersions: SavedObjectsModelVersionMap = {
|
||||
'1': {
|
||||
|
@ -16,4 +16,11 @@ export const ruleModelVersions: SavedObjectsModelVersionMap = {
|
|||
create: rawRuleSchemaV1,
|
||||
},
|
||||
},
|
||||
'2': {
|
||||
changes: [],
|
||||
schemas: {
|
||||
forwardCompatibility: rawRuleSchemaV2.extends({}, { unknowns: 'ignore' }),
|
||||
create: rawRuleSchemaV2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
*/
|
||||
|
||||
export { rawRuleSchema as rawRuleSchemaV1 } from './v1';
|
||||
export { rawRuleSchema as rawRuleSchemaV2 } from './v2';
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { rawRuleSchema as rawRuleSchemaV1 } from './v1';
|
||||
|
||||
export const flappingSchema = schema.object({
|
||||
lookBackWindow: schema.number(),
|
||||
statusChangeThreshold: schema.number(),
|
||||
});
|
||||
|
||||
export const rawRuleSchema = rawRuleSchemaV1.extends({
|
||||
flapping: schema.maybe(schema.nullable(flappingSchema)),
|
||||
});
|
|
@ -290,11 +290,22 @@ export class TaskRunner<
|
|||
state: { previousStartedAt },
|
||||
} = this.taskInstance;
|
||||
|
||||
const { queryDelaySettings, flappingSettings } =
|
||||
const { queryDelaySettings, flappingSettings: spaceFlappingSettings } =
|
||||
await this.context.rulesSettingsService.getSettings(fakeRequest, spaceId);
|
||||
const ruleRunMetricsStore = new RuleRunMetricsStore();
|
||||
const ruleLabel = `${this.ruleType.id}:${ruleId}: '${rule.name}'`;
|
||||
|
||||
const ruleFlappingSettings = rule.flapping
|
||||
? {
|
||||
enabled: true,
|
||||
...rule.flapping,
|
||||
}
|
||||
: null;
|
||||
|
||||
const flappingSettings = spaceFlappingSettings.enabled
|
||||
? ruleFlappingSettings || spaceFlappingSettings
|
||||
: spaceFlappingSettings;
|
||||
|
||||
const ruleTypeRunnerContext = {
|
||||
alertingEventLogger: this.alertingEventLogger,
|
||||
flappingSettings,
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
AlertInstanceContext,
|
||||
Rule,
|
||||
RuleAlertData,
|
||||
RawRule,
|
||||
MaintenanceWindowStatus,
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
DEFAULT_QUERY_DELAY_SETTINGS,
|
||||
|
@ -105,6 +106,7 @@ import {
|
|||
import { backfillClientMock } from '../backfill_client/backfill_client.mock';
|
||||
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
|
||||
import { createTaskRunnerLogger } from './lib';
|
||||
import { SavedObject } from '@kbn/core/server';
|
||||
import { maintenanceWindowsServiceMock } from './maintenance_windows/maintenance_windows_service.mock';
|
||||
import { getMockMaintenanceWindow } from '../data/maintenance_window/test_helpers';
|
||||
import { rulesSettingsServiceMock } from '../rules_settings/rules_settings_service.mock';
|
||||
|
@ -824,6 +826,112 @@ describe('Task Runner', () => {
|
|||
spy1.mockRestore();
|
||||
spy2.mockRestore();
|
||||
});
|
||||
|
||||
test('should use rule specific flapping settings if global flapping is enabled', async () => {
|
||||
mockAlertsService.createAlertsClient.mockImplementation(() => mockAlertsClient);
|
||||
mockAlertsClient.getAlertsToSerialize.mockResolvedValue({
|
||||
alertsToReturn: {},
|
||||
recoveredAlertsToReturn: {},
|
||||
});
|
||||
|
||||
const taskRunner = new TaskRunner({
|
||||
ruleType: ruleTypeWithAlerts,
|
||||
internalSavedObjectsRepository,
|
||||
taskInstance: {
|
||||
...mockedTaskInstance,
|
||||
state: {
|
||||
...mockedTaskInstance.state,
|
||||
previousStartedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
||||
},
|
||||
},
|
||||
context: taskRunnerFactoryInitializerParams,
|
||||
inMemoryMetrics,
|
||||
});
|
||||
|
||||
const ruleSpecificFlapping = {
|
||||
lookBackWindow: 10,
|
||||
statusChangeThreshold: 10,
|
||||
};
|
||||
|
||||
mockGetAlertFromRaw.mockReturnValue({
|
||||
...mockedRuleTypeSavedObject,
|
||||
flapping: ruleSpecificFlapping,
|
||||
} as Rule);
|
||||
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({
|
||||
...mockedRawRuleSO,
|
||||
flapping: ruleSpecificFlapping,
|
||||
} as SavedObject<RawRule>);
|
||||
|
||||
await taskRunner.run();
|
||||
|
||||
expect(mockAlertsClient.initializeExecution).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
flappingSettings: {
|
||||
enabled: true,
|
||||
...ruleSpecificFlapping,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('should not use rule specific flapping settings if global flapping is disabled', async () => {
|
||||
rulesSettingsService.getSettings.mockResolvedValue({
|
||||
flappingSettings: {
|
||||
enabled: false,
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 20,
|
||||
},
|
||||
queryDelaySettings: DEFAULT_QUERY_DELAY_SETTINGS,
|
||||
});
|
||||
|
||||
mockAlertsService.createAlertsClient.mockImplementation(() => mockAlertsClient);
|
||||
mockAlertsClient.getAlertsToSerialize.mockResolvedValue({
|
||||
alertsToReturn: {},
|
||||
recoveredAlertsToReturn: {},
|
||||
});
|
||||
|
||||
const taskRunner = new TaskRunner({
|
||||
ruleType: ruleTypeWithAlerts,
|
||||
internalSavedObjectsRepository,
|
||||
taskInstance: {
|
||||
...mockedTaskInstance,
|
||||
state: {
|
||||
...mockedTaskInstance.state,
|
||||
previousStartedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
||||
},
|
||||
},
|
||||
context: taskRunnerFactoryInitializerParams,
|
||||
inMemoryMetrics,
|
||||
});
|
||||
|
||||
const ruleSpecificFlapping = {
|
||||
lookBackWindow: 10,
|
||||
statusChangeThreshold: 10,
|
||||
};
|
||||
|
||||
mockGetAlertFromRaw.mockReturnValue({
|
||||
...mockedRuleTypeSavedObject,
|
||||
flapping: ruleSpecificFlapping,
|
||||
} as Rule);
|
||||
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({
|
||||
...mockedRawRuleSO,
|
||||
flapping: ruleSpecificFlapping,
|
||||
} as SavedObject<RawRule>);
|
||||
|
||||
await taskRunner.run();
|
||||
|
||||
expect(mockAlertsClient.initializeExecution).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
flappingSettings: {
|
||||
enabled: false,
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 20,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function testCorrectAlertsClientUsed<
|
||||
|
|
|
@ -11,7 +11,7 @@ import type {
|
|||
SavedObjectReference,
|
||||
IUiSettingsClient,
|
||||
} from '@kbn/core/server';
|
||||
import z from '@kbn/zod';
|
||||
import { z } from '@kbn/zod';
|
||||
import { DataViewsContract } from '@kbn/data-views-plugin/common';
|
||||
import { ISearchStartSearchSource } from '@kbn/data-plugin/common';
|
||||
import { LicenseType } from '@kbn/licensing-plugin/server';
|
||||
|
@ -64,6 +64,7 @@ import {
|
|||
AlertsFilterTimeframe,
|
||||
RuleAlertData,
|
||||
AlertDelay,
|
||||
Flapping,
|
||||
} from '../common';
|
||||
import { PublicAlertFactory } from './alert/create_alert_factory';
|
||||
import { RulesSettingsFlappingProperties } from '../common/rules_settings';
|
||||
|
@ -77,7 +78,7 @@ export type { RuleTypeParams };
|
|||
*/
|
||||
export interface AlertingApiRequestHandlerContext {
|
||||
getRulesClient: () => RulesClient;
|
||||
getRulesSettingsClient: () => RulesSettingsClient;
|
||||
getRulesSettingsClient: (withoutAuth?: boolean) => RulesSettingsClient;
|
||||
getMaintenanceWindowClient: () => MaintenanceWindowClient;
|
||||
listTypes: RuleTypeRegistry['list'];
|
||||
getFrameworkHealth: () => Promise<AlertsHealth>;
|
||||
|
@ -505,6 +506,7 @@ export interface RawRule extends SavedObjectAttributes {
|
|||
revision: number;
|
||||
running?: boolean | null;
|
||||
alertDelay?: AlertDelay;
|
||||
flapping?: Flapping | null;
|
||||
}
|
||||
|
||||
export type { DataStreamAdapter } from './alerts_service/lib/data_stream_adapter';
|
||||
|
|
|
@ -57,6 +57,16 @@ const transformLastRun: RewriteRequestCase<RuleLastRun> = ({
|
|||
...rest,
|
||||
});
|
||||
|
||||
const transformFlapping = (flapping: AsApiContract<Rule['flapping']>) => {
|
||||
if (!flapping) {
|
||||
return flapping;
|
||||
}
|
||||
return {
|
||||
lookBackWindow: flapping.look_back_window,
|
||||
statusChangeThreshold: flapping.status_change_threshold,
|
||||
};
|
||||
};
|
||||
|
||||
export const transformRule: RewriteRequestCase<Rule> = ({
|
||||
rule_type_id: ruleTypeId,
|
||||
created_by: createdBy,
|
||||
|
@ -77,6 +87,7 @@ export const transformRule: RewriteRequestCase<Rule> = ({
|
|||
last_run: lastRun,
|
||||
next_run: nextRun,
|
||||
alert_delay: alertDelay,
|
||||
flapping,
|
||||
...rest
|
||||
}: any) => ({
|
||||
ruleTypeId,
|
||||
|
@ -100,6 +111,7 @@ export const transformRule: RewriteRequestCase<Rule> = ({
|
|||
...(nextRun ? { nextRun } : {}),
|
||||
...(apiKeyCreatedByUser !== undefined ? { apiKeyCreatedByUser } : {}),
|
||||
...(alertDelay ? { alertDelay } : {}),
|
||||
...(flapping !== undefined ? { flapping: transformFlapping(flapping) } : {}),
|
||||
...rest,
|
||||
});
|
||||
|
||||
|
|
|
@ -62,7 +62,6 @@ import {
|
|||
isActionGroupDisabledForActionTypeId,
|
||||
RuleActionAlertsFilterProperty,
|
||||
RuleActionKey,
|
||||
RuleSpecificFlappingProperties,
|
||||
} from '@kbn/alerting-plugin/common';
|
||||
import { AlertingConnectorFeatureId } from '@kbn/actions-plugin/common';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
|
@ -415,10 +414,6 @@ export const RuleForm = ({
|
|||
dispatch({ command: { type: 'setAlertDelayProperty' }, payload: { key, value } });
|
||||
};
|
||||
|
||||
const setFlapping = (flapping: RuleSpecificFlappingProperties | null) => {
|
||||
dispatch({ command: { type: 'setProperty' }, payload: { key: 'flapping', value: flapping } });
|
||||
};
|
||||
|
||||
const onAlertDelayChange = (value: string) => {
|
||||
const parsedValue = value === '' ? '' : parseInt(value, 10);
|
||||
setAlertDelayProperty('active', parsedValue || 1);
|
||||
|
@ -887,7 +882,7 @@ export const RuleForm = ({
|
|||
alertDelay={alertDelay}
|
||||
flappingSettings={rule.flapping}
|
||||
onAlertDelayChange={onAlertDelayChange}
|
||||
onFlappingChange={setFlapping}
|
||||
onFlappingChange={(flapping) => setRuleProperty('flapping', flapping)}
|
||||
enabledFlapping={IS_RULE_SPECIFIC_FLAPPING_ENABLED}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
|
|
|
@ -31,8 +31,9 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { RuleSettingsFlappingInputs } from '@kbn/alerts-ui-shared/src/rule_settings/rule_settings_flapping_inputs';
|
||||
import { RuleSettingsFlappingMessage } from '@kbn/alerts-ui-shared/src/rule_settings/rule_settings_flapping_message';
|
||||
import { RuleSpecificFlappingProperties } from '@kbn/alerting-plugin/common';
|
||||
import { Rule } from '@kbn/alerts-ui-shared';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { Flapping } from '@kbn/alerting-plugin/common';
|
||||
import { useGetFlappingSettings } from '../../hooks/use_get_flapping_settings';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
|
||||
|
@ -146,7 +147,10 @@ const flappingTitlePopoverLookBack = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
const clampFlappingValues = (flapping: RuleSpecificFlappingProperties) => {
|
||||
const clampFlappingValues = (flapping: Rule['flapping']) => {
|
||||
if (!flapping) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
...flapping,
|
||||
statusChangeThreshold: Math.min(flapping.lookBackWindow, flapping.statusChangeThreshold),
|
||||
|
@ -157,9 +161,9 @@ const INTEGER_REGEX = /^[1-9][0-9]*$/;
|
|||
|
||||
export interface RuleFormAdvancedOptionsProps {
|
||||
alertDelay?: number;
|
||||
flappingSettings?: RuleSpecificFlappingProperties;
|
||||
flappingSettings?: Flapping | null;
|
||||
onAlertDelayChange: (value: string) => void;
|
||||
onFlappingChange: (value: RuleSpecificFlappingProperties | null) => void;
|
||||
onFlappingChange: (value: Flapping | null) => void;
|
||||
enabledFlapping?: boolean;
|
||||
}
|
||||
|
||||
|
@ -183,7 +187,7 @@ export const RuleFormAdvancedOptions = (props: RuleFormAdvancedOptionsProps) =>
|
|||
const [isFlappingOffPopoverOpen, setIsFlappingOffPopoverOpen] = useState<boolean>(false);
|
||||
const [isFlappingTitlePopoverOpen, setIsFlappingTitlePopoverOpen] = useState<boolean>(false);
|
||||
|
||||
const cachedFlappingSettings = useRef<RuleSpecificFlappingProperties>();
|
||||
const cachedFlappingSettings = useRef<Flapping>();
|
||||
|
||||
const isDesktop = useIsWithinMinBreakpoint('xl');
|
||||
|
||||
|
@ -204,8 +208,11 @@ export const RuleFormAdvancedOptions = (props: RuleFormAdvancedOptionsProps) =>
|
|||
);
|
||||
|
||||
const internalOnFlappingChange = useCallback(
|
||||
(flapping: RuleSpecificFlappingProperties) => {
|
||||
(flapping: Flapping) => {
|
||||
const clampedValue = clampFlappingValues(flapping);
|
||||
if (!clampedValue) {
|
||||
return;
|
||||
}
|
||||
onFlappingChange(clampedValue);
|
||||
cachedFlappingSettings.current = clampedValue;
|
||||
},
|
||||
|
@ -407,7 +414,7 @@ export const RuleFormAdvancedOptions = (props: RuleFormAdvancedOptionsProps) =>
|
|||
{flappingOffTooltip}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{flappingSettings && (
|
||||
{flappingSettings && enabled && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiHorizontalRule margin="none" />
|
||||
|
@ -425,6 +432,9 @@ export const RuleFormAdvancedOptions = (props: RuleFormAdvancedOptionsProps) =>
|
|||
]);
|
||||
|
||||
const flappingFormBody = useMemo(() => {
|
||||
if (!spaceFlappingSettings || !spaceFlappingSettings.enabled) {
|
||||
return null;
|
||||
}
|
||||
if (!flappingSettings) {
|
||||
return null;
|
||||
}
|
||||
|
@ -438,7 +448,12 @@ export const RuleFormAdvancedOptions = (props: RuleFormAdvancedOptionsProps) =>
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}, [flappingSettings, onLookBackWindowChange, onStatusChangeThresholdChange]);
|
||||
}, [
|
||||
flappingSettings,
|
||||
spaceFlappingSettings,
|
||||
onLookBackWindowChange,
|
||||
onStatusChangeThresholdChange,
|
||||
]);
|
||||
|
||||
const flappingFormMessage = useMemo(() => {
|
||||
if (!spaceFlappingSettings || !spaceFlappingSettings.enabled) {
|
||||
|
|
|
@ -66,6 +66,10 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
params: {},
|
||||
},
|
||||
],
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -126,6 +130,10 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
muted_alert_ids: [],
|
||||
execution_status: response.body.execution_status,
|
||||
revision: 0,
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
|
||||
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
|
||||
});
|
||||
|
|
|
@ -80,6 +80,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
};
|
||||
const response = await supertestWithoutAuth
|
||||
.put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
|
@ -138,6 +142,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
updated_at: response.body.updated_at,
|
||||
execution_status: response.body.execution_status,
|
||||
revision: 1,
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
|
||||
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
|
||||
});
|
||||
|
@ -185,6 +193,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
};
|
||||
const response = await supertestWithoutAuth
|
||||
.put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
|
@ -231,6 +243,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
updated_at: response.body.updated_at,
|
||||
execution_status: response.body.execution_status,
|
||||
revision: 1,
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
|
||||
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
|
||||
});
|
||||
|
@ -278,6 +294,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
};
|
||||
const response = await supertestWithoutAuth
|
||||
.put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
|
@ -324,6 +344,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
updated_at: response.body.updated_at,
|
||||
execution_status: response.body.execution_status,
|
||||
revision: 1,
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
|
||||
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
|
||||
});
|
||||
|
@ -371,6 +395,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
};
|
||||
const response = await supertestWithoutAuth
|
||||
.put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
|
@ -424,6 +452,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
updated_at: response.body.updated_at,
|
||||
execution_status: response.body.execution_status,
|
||||
revision: 1,
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
|
||||
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
|
||||
});
|
||||
|
@ -480,6 +512,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
};
|
||||
const response = await supertestWithoutAuth
|
||||
.put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
|
@ -522,6 +558,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
updated_at: response.body.updated_at,
|
||||
execution_status: response.body.execution_status,
|
||||
revision: 1,
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
...(response.body.next_run ? { next_run: response.body.next_run } : {}),
|
||||
...(response.body.last_run ? { last_run: response.body.last_run } : {}),
|
||||
});
|
||||
|
@ -564,6 +604,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onActiveAlert',
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
};
|
||||
const response = await supertestWithoutAuth
|
||||
.put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`)
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
ObjectRemover,
|
||||
getUnauthorizedErrorMessage,
|
||||
TaskManagerDoc,
|
||||
resetRulesSettings,
|
||||
} from '../../../../common/lib';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
|
||||
|
@ -635,6 +636,90 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
describe('create rule flapping', () => {
|
||||
afterEach(async () => {
|
||||
await resetRulesSettings(supertest, 'space1');
|
||||
});
|
||||
|
||||
it('should allow flapping to be created', async () => {
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
flapping: {
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.eql(200);
|
||||
objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting');
|
||||
|
||||
expect(response.body.flapping).to.eql({
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if flapping is created when global flapping is off', async () => {
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/settings/_flapping`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
enabled: false,
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
});
|
||||
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
flapping: {
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.statusCode).eql(400);
|
||||
expect(response.body.message).eql(
|
||||
'Error creating rule: can not create rule with flapping if global flapping is disabled'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if flapping is invalid', async () => {
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
flapping: {
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
})
|
||||
)
|
||||
.expect(400);
|
||||
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
flapping: {
|
||||
look_back_window: -5,
|
||||
status_change_threshold: -5,
|
||||
},
|
||||
})
|
||||
)
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', function () {
|
||||
|
|
|
@ -8,7 +8,13 @@
|
|||
import expect from '@kbn/expect';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server';
|
||||
import { Spaces } from '../../../scenarios';
|
||||
import { checkAAD, getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib';
|
||||
import {
|
||||
checkAAD,
|
||||
getUrlPrefix,
|
||||
getTestRuleData,
|
||||
ObjectRemover,
|
||||
resetRulesSettings,
|
||||
} from '../../../../common/lib';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@ -177,6 +183,233 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('update rule flapping', () => {
|
||||
afterEach(async () => {
|
||||
await resetRulesSettings(supertest, 'space1');
|
||||
});
|
||||
|
||||
it('should allow flapping to be updated', async () => {
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData());
|
||||
|
||||
expect(response.body.flapping).eql(undefined);
|
||||
objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting');
|
||||
|
||||
const { body: updatedRule } = await supertest
|
||||
.put(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${response.body.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'bcd',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
schedule: { interval: '12s' },
|
||||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
flapping: {
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
},
|
||||
});
|
||||
|
||||
expect(updatedRule.flapping).eql({
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow flapping to be removed via update', async () => {
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
flapping: {
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.body.flapping).eql({
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
});
|
||||
|
||||
objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting');
|
||||
|
||||
const { body: updatedRule } = await supertest
|
||||
.put(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${response.body.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'bcd',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
schedule: { interval: '12s' },
|
||||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
flapping: null,
|
||||
});
|
||||
|
||||
expect(updatedRule.flapping).eql(null);
|
||||
});
|
||||
|
||||
it('should throw if flapping is updated when global flapping is off', async () => {
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData());
|
||||
|
||||
objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting');
|
||||
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/settings/_flapping`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
enabled: false,
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
});
|
||||
|
||||
await supertest
|
||||
.put(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${response.body.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'bcd',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
schedule: { interval: '12s' },
|
||||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
flapping: {
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
},
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should allow rule to be updated when global flapping is off if not updating flapping', async () => {
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
flapping: {
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting');
|
||||
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/settings/_flapping`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
enabled: false,
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
});
|
||||
|
||||
await supertest
|
||||
.put(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${response.body.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'updated name 1',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
schedule: { interval: '12s' },
|
||||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.put(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${response.body.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'updated name 2',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
schedule: { interval: '12s' },
|
||||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
flapping: {
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 5,
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('should throw if flapping is invalid', async () => {
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(getTestRuleData());
|
||||
|
||||
objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting');
|
||||
|
||||
await supertest
|
||||
.put(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${response.body.id}`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'bcd',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
schedule: { interval: '12s' },
|
||||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
flapping: {
|
||||
look_back_window: 5,
|
||||
status_change_threshold: 10,
|
||||
},
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'bcd',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
schedule: { interval: '12s' },
|
||||
actions: [],
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
flapping: {
|
||||
look_back_window: -5,
|
||||
status_change_threshold: -5,
|
||||
},
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', function () {
|
||||
this.tags('skipFIPS');
|
||||
it('should handle update alert request appropriately', async () => {
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
getTestRuleData,
|
||||
getUrlPrefix,
|
||||
ObjectRemover,
|
||||
resetRulesSettings,
|
||||
TaskManagerDoc,
|
||||
} from '../../../../../common/lib';
|
||||
import { TEST_CACHE_EXPIRATION_TIME } from '../../create_test_data';
|
||||
|
@ -45,6 +46,10 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
|
|||
await objectRemover.removeAll();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await resetRulesSettings(supertestWithoutAuth, 'space1');
|
||||
});
|
||||
|
||||
// These are the same tests from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts
|
||||
// but testing that flapping status & flapping history is updated as expected for AAD docs
|
||||
|
||||
|
@ -535,6 +540,203 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
|
|||
expect(alertDocs[0]._source![ALERT_FLAPPING]).to.equal(false);
|
||||
expect(state.alertInstances.alertA.meta.flapping).to.equal(false);
|
||||
});
|
||||
|
||||
it('should allow rule specific flapping to override space flapping', async () => {
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/settings/_flapping`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth('superuser', 'superuser')
|
||||
.send({
|
||||
enabled: true,
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 2,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const pattern = {
|
||||
alertA: [true, false, true, false, true, false, true, false],
|
||||
};
|
||||
|
||||
const ruleParameters = { pattern };
|
||||
const createdRule1 = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
rule_type_id: 'test.patternFiringAad',
|
||||
// set the schedule long so we can use "runSoon" to specify rule runs
|
||||
schedule: { interval: '1d' },
|
||||
throttle: null,
|
||||
params: ruleParameters,
|
||||
actions: [],
|
||||
notify_when: RuleNotifyWhen.CHANGE,
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
|
||||
const rule1Id = createdRule1.body.id;
|
||||
objectRemover.add(Spaces.space1.id, rule1Id, 'rule', 'alerting');
|
||||
|
||||
// Wait for the rule to run once
|
||||
let run = 1;
|
||||
let runWhichItFlapped = 0;
|
||||
|
||||
await waitForEventLogDocs(rule1Id, new Map([['execute', { equal: 1 }]]));
|
||||
// Run them all
|
||||
for (let i = 0; i < 7; i++) {
|
||||
await retry.try(async () => {
|
||||
const response = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${rule1Id}/_run_soon`)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
expect(response.status).to.eql(204);
|
||||
});
|
||||
|
||||
await waitForEventLogDocs(rule1Id, new Map([['execute', { equal: ++run }]]));
|
||||
|
||||
const alertDocs = await queryForAlertDocs<PatternFiringAlert>(rule1Id);
|
||||
const isFlapping = alertDocs[0]._source![ALERT_FLAPPING];
|
||||
|
||||
if (!runWhichItFlapped && isFlapping) {
|
||||
runWhichItFlapped = run;
|
||||
}
|
||||
}
|
||||
|
||||
// Flapped on the 4th run
|
||||
expect(runWhichItFlapped).eql(4);
|
||||
|
||||
// Create a rule with flapping
|
||||
const createdRule2 = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
rule_type_id: 'test.patternFiringAad',
|
||||
// set the schedule long so we can use "runSoon" to specify rule runs
|
||||
schedule: { interval: '1d' },
|
||||
throttle: null,
|
||||
params: ruleParameters,
|
||||
actions: [],
|
||||
notify_when: RuleNotifyWhen.CHANGE,
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 4,
|
||||
},
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
|
||||
const rule2Id = createdRule2.body.id;
|
||||
objectRemover.add(Spaces.space1.id, rule2Id, 'rule', 'alerting');
|
||||
|
||||
// Wait for the rule to run once
|
||||
run = 1;
|
||||
runWhichItFlapped = 0;
|
||||
|
||||
await waitForEventLogDocs(rule2Id, new Map([['execute', { equal: 1 }]]));
|
||||
// Run them all
|
||||
for (let i = 0; i < 7; i++) {
|
||||
await retry.try(async () => {
|
||||
const response = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${rule2Id}/_run_soon`)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
expect(response.status).to.eql(204);
|
||||
});
|
||||
|
||||
await waitForEventLogDocs(rule2Id, new Map([['execute', { equal: ++run }]]));
|
||||
|
||||
const alertDocs = await queryForAlertDocs<PatternFiringAlert>(rule2Id);
|
||||
const isFlapping = alertDocs[0]._source![ALERT_FLAPPING];
|
||||
|
||||
if (!runWhichItFlapped && isFlapping) {
|
||||
runWhichItFlapped = run;
|
||||
}
|
||||
}
|
||||
|
||||
// Flapped on the 6th run, which is more than the space status change threshold
|
||||
expect(runWhichItFlapped).eql(6);
|
||||
});
|
||||
|
||||
it('should ignore rule flapping if the space flapping is disabled', async () => {
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/settings/_flapping`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth('superuser', 'superuser')
|
||||
.send({
|
||||
enabled: true,
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 2,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const pattern = {
|
||||
alertA: [true, false, true, false, true, false, true, false],
|
||||
};
|
||||
|
||||
const ruleParameters = { pattern };
|
||||
// Create a rule with flapping
|
||||
const createdRule = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestRuleData({
|
||||
rule_type_id: 'test.patternFiringAad',
|
||||
// set the schedule long so we can use "runSoon" to specify rule runs
|
||||
schedule: { interval: '1d' },
|
||||
throttle: null,
|
||||
params: ruleParameters,
|
||||
actions: [],
|
||||
notify_when: RuleNotifyWhen.CHANGE,
|
||||
flapping: {
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 4,
|
||||
},
|
||||
})
|
||||
)
|
||||
.expect(200);
|
||||
|
||||
const ruleId = createdRule.body.id;
|
||||
objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting');
|
||||
|
||||
// Turn global flapping off, need to do this after the rule is created because
|
||||
// we do not allow rules to be created with flapping if global flapping is off.
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/settings/_flapping`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth('superuser', 'superuser')
|
||||
.send({
|
||||
enabled: false,
|
||||
look_back_window: 10,
|
||||
status_change_threshold: 2,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
// Wait for the rule to run once
|
||||
let run = 1;
|
||||
let runWhichItFlapped = 0;
|
||||
|
||||
await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 1 }]]));
|
||||
// Run them all
|
||||
for (let i = 0; i < 7; i++) {
|
||||
await retry.try(async () => {
|
||||
const response = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
expect(response.status).to.eql(204);
|
||||
});
|
||||
|
||||
await waitForEventLogDocs(ruleId, new Map([['execute', { equal: ++run }]]));
|
||||
|
||||
const alertDocs = await queryForAlertDocs<PatternFiringAlert>(ruleId);
|
||||
const isFlapping = alertDocs[0]._source![ALERT_FLAPPING];
|
||||
|
||||
if (!runWhichItFlapped && isFlapping) {
|
||||
runWhichItFlapped = run;
|
||||
}
|
||||
}
|
||||
|
||||
// Never flapped, since globl flapping is off
|
||||
expect(runWhichItFlapped).eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
async function getRuleState(ruleId: string) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue