[OAS] Support examples (and more) (#212495)

## Summary

Close https://github.com/elastic/kibana/issues/188926

Adds the ability to merge in an arbitrary OAS operation object (search
[spec](https://swagger.io/specification/) for "operation object") in
code generated OAS.

This enables us to write examples next to the code:

<img width="306" alt="Screenshot 2025-02-26 at 11 24 00"
src="https://github.com/user-attachments/assets/5df92ae9-c8c5-4797-8b7c-764e937e84e9"
/>

```ts
// Inside the router declaration
  router.post(
    {
      path: `${BASE_ALERTING_API_PATH}/rule/{id?}`,
      security: DEFAULT_ALERTING_ROUTE_SECURITY,
      options: {
        access: 'public',
        summary: `Create a rule`,
        tags: ['oas-tag:alerting'],
        // NEW 👇🏻: this file can contain operation-level OAS and will merge/override anything that we auto generate
        oasOperationObject: () => path.join(__dirname, 'create_rule.examples.yaml'),
        // ☝🏻 can also be a TS-checked OpenAPIV3.OperationObject
```

Tested with create rule example overlay
([gist](https://gist.github.com/jloleysens/dc643522a3f58dc2aed3dcef966b42df))
and bump

<img width="1236" alt="Screenshot 2025-02-26 at 11 45 57"
src="https://github.com/user-attachments/assets/c21b466a-ddab-49ce-b4ba-a04fd0e6c1b7"
/>

## Docs

Added developer guide docs to
https://docs.elastic.dev/kibana-dev-docs/genereating-oas-for-http-apis#2-route-definitions

<details>

<summary>images</summary>

<img width="799" alt="Screenshot 2025-03-13 at 13 02 31"
src="https://github.com/user-attachments/assets/e89b2c5a-1984-4672-a40b-b492581e690f"
/>

<img width="819" alt="Screenshot 2025-03-13 at 13 02 39"
src="https://github.com/user-attachments/assets/1375a25a-4d91-46b4-8ce5-42c763657d96"
/>

</details>




### TO DO
- [x] Document the feature in TS
- [x] Document feature in dev docs
- [x] Add tests

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Jean-Louis Leysens 2025-03-14 14:56:28 +01:00 committed by GitHub
parent f2af002a5e
commit 5e90e557cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 4146 additions and 1652 deletions

View file

@ -92,7 +92,7 @@ import { fooResource } from '../../schemas/v1';
// Note: this response schema is instantiated lazily to avoid creating schemas that are not needed in most cases!
const fooResourceResponse = () => {
return schema.object({
id: schema.string({
id: schema.string({
maxLength: 20,
meta: { description: 'Add a description.' }
}),
@ -129,7 +129,7 @@ function registerFooRoute(router: IRouter, docLinks: DoclinksStart) {
version: '2023-10-31',
validate: {
request: {
body: ,
body: fooResource,
},
response: {
200: {
@ -155,6 +155,124 @@ function registerFooRoute(router: IRouter, docLinks: DoclinksStart) {
}
```
##### Adding examples
Beyond the schema of requests and responses, it is **very useful** to provide
concrete requests and responses as examples. Examples go beyond defaults and
provide a more intuitive understanding for end users in learning the behaviour
of your API. See the [bump.sh documentation](https://docs.bump.sh/guides/openapi/specification/v3.1/data-models/examples/)
for more information on how examples will be shown to end users.
To add examples to the endpoint we created above you could do the following:
```typescript
// ...
.addVersion({
version: '2023-10-31',
options: {
// Be sure and lazily instantiate this value. It's only used at dev time!
oasOperationObject: () => ({
requestBody: {
content: {
'application/json': {
examples: {
fooExample1: {
summary: 'An example foo request',
value: {
name: 'Cool foo!',
} as FooResource,
},
},
},
},
},
responses: {
200: {
content: {
'application/json': {
examples: {
/* Put your 200 response examples here */
},
},
},
},
},
}),
},
validate: {
request: {
body: fooResource,
},
response: {
200: {
body: fooResourceResponse,
},
},
},
},
// ...
```
The strength of this approach is your examples are captured in code and type
checked at dev time. So any shape errors should be caught as you author.
<details>
<summary>I have prexisting YAML based examples I'd like to use!</summary>
If you pre-existing examples created in YAML that you would like
to use the following approach:
```typescript
import path from 'node:path';
const oasOperationObject: () => path.join(__dirname, 'foo.examples.yaml'),
// ...
.addVersion({
version: '2023-10-31',
options: {
oasOperationObject,
},
validate: {
request: {
body: fooResource,
},
response: {
200: {
body: fooResourceResponse,
},
},
},
},
// ...
```
Where the contents of `foo.examples.yaml` are:
```yaml
requestBody:
content:
application/json:
examples: # Make sure to use the examples array, example (singular) has been deprecated
fooExample:
summary: Foo example
description: >
An example request of creating foo.
value:
name: 'Cool foo!'
fooExampleRef:
# You can use JSONSchema $refs to organize this file further
$ref: "./examples/foo_example_i_factored_out_of_this_file.yaml"
responses:
200:
content:
application/json:
examples:
# Apply a similar pattern to writing examples here
```
</details>
#### 3. Generating OAS
See <a href="#how-do-i-see-my-http-apis-oas">this section</a> about viewing your HTTP APIs OAS.

View file

@ -1777,6 +1777,206 @@
"requestBody": {
"content": {
"application/json": {
"examples": {
"createEsQueryEsqlRuleRequest": {
"description": "Create an Elasticsearch query rule that uses Elasticsearch Query Language (ES|QL) to define its query and a server log connector to send notifications.\n",
"summary": "Elasticsearch query rule (ES|QL)",
"value": {
"actions": [
{
"frequency": {
"notify_when": "onActiveAlert",
"summary": false
},
"group": "query matched",
"id": "d0db1fe0-78d6-11ee-9177-f7d404c8c945",
"params": {
"level": "info",
"message": "Elasticsearch query rule '{{rule.name}}' is active:\n- Value: {{context.value}} - Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}} - Timestamp: {{context.date}} - Link: {{context.link}}"
}
}
],
"consumer": "stackAlerts",
"name": "my Elasticsearch query ESQL rule",
"params": {
"esqlQuery": {
"esql": "FROM kibana_sample_data_logs | KEEP bytes, clientip, host, geo.dest | where geo.dest != \"GB\" | STATS sumbytes = sum(bytes) by clientip, host | WHERE sumbytes > 5000 | SORT sumbytes desc | LIMIT 10"
},
"searchType": "esqlQuery",
"size": 0,
"threshold": [
0
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 1,
"timeWindowUnit": "d"
},
"rule_type_id": ".es-query",
"schedule": {
"interval": "1d"
}
}
},
"createEsQueryKqlRuleRequest": {
"description": "Create an Elasticsearch query rule that uses Kibana query language (KQL).",
"summary": "Elasticsearch query rule (KQL)",
"value": {
"consumer": "alerts",
"name": "my Elasticsearch query KQL rule",
"params": {
"aggType": "count",
"excludeHitsFromPreviousRun": true,
"groupBy": "all",
"searchConfiguration": {
"index": "90943e30-9a47-11e8-b64d-95841ca0b247",
"query": {
"language": "kuery",
"query": "\"\"geo.src : \"US\" \"\""
}
},
"searchType": "searchSource",
"size": 100,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"rule_type_id": ".es-query",
"schedule": {
"interval": "1m"
}
}
},
"createEsQueryRuleRequest": {
"description": "Create an Elasticsearch query rule that uses Elasticsearch query domain specific language (DSL) to define its query and a server log connector to send notifications.\n",
"summary": "Elasticsearch query rule (DSL)",
"value": {
"actions": [
{
"frequency": {
"notify_when": "onThrottleInterval",
"summary": true,
"throttle": "1d"
},
"group": "query matched",
"id": "fdbece50-406c-11ee-850e-c71febc4ca7f",
"params": {
"level": "info",
"message": "The system has detected {{alerts.new.count}} new, {{alerts.ongoing.count}} ongoing, and {{alerts.recovered.count}} recovered alerts."
}
},
{
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false
},
"group": "recovered",
"id": "fdbece50-406c-11ee-850e-c71febc4ca7f",
"params": {
"level": "info",
"message": "Recovered"
}
}
],
"consumer": "alerts",
"name": "my Elasticsearch query rule",
"params": {
"esQuery": "\"\"\"{\"query\":{\"match_all\" : {}}}\"\"\"",
"index": [
"kibana_sample_data_logs"
],
"size": 100,
"threshold": [
100
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 1,
"timeWindowUnit": "d"
},
"rule_type_id": ".es-query",
"schedule": {
"interval": "1d"
}
}
},
"createIndexThresholdRuleRequest": {
"description": "Create an index threshold rule that uses a server log connector to send notifications when the threshold is met.\n",
"summary": "Index threshold rule",
"value": {
"actions": [
{
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false
},
"group": "threshold met",
"id": "48de3460-f401-11ed-9f8e-399c75a2deeb",
"params": {
"level": "info",
"message": "Rule '{{rule.name}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
}
}
],
"alert_delay": {
"active": 3
},
"consumer": "alerts",
"name": "my rule",
"params": {
"aggField": "sheet.version",
"aggType": "avg",
"groupBy": "top",
"index": [
".test-index"
],
"termField": "name.keyword",
"termSize": 6,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"rule_type_id": ".index-threshold",
"schedule": {
"interval": "1m"
},
"tags": [
"cpu"
]
}
},
"createTrackingContainmentRuleRequest": {
"description": "Create a tracking containment rule that checks when an entity is contained or no longer contained within a boundary.\n",
"summary": "Tracking containment rule",
"value": {
"consumer": "alerts",
"name": "my tracking rule",
"params": {
"boundaryGeoField": "location",
"boundaryIndexId": "0cd90abf-abe7-44c7-909a-f621bbbcfefc",
"boundaryIndexTitle": "boundary*",
"boundaryNameField": "name",
"boundaryType": "entireIndex",
"dateField\"": "@timestamp",
"entity": "agent.keyword",
"geoField": "geo.coordinates",
"index": "kibana_sample_data_logs",
"indexId": "90943e30-9a47-11e8-b64d-95841ca0b247"
},
"rule_type_id": ".geo-containment",
"schedule": {
"interval": "1h"
}
}
}
},
"schema": {
"additionalProperties": false,
"properties": {
@ -2073,6 +2273,339 @@
"200": {
"content": {
"application/json": {
"examples": {
"createEsQueryEsqlRuleResponse": {
"description": "The response for successfully creating an Elasticsearch query rule that uses Elasticsearch Query Language (ES|QL).",
"summary": "Elasticsearch query rule (ES|QL)",
"value": {
"actions": [
{
"connector_type_id": ".server-log",
"frequency": {
"notify_when": "onActiveAlert",
"summary": false,
"throttle": null
},
"group": "query matched",
"id": "d0db1fe0-78d6-11ee-9177-f7d404c8c945",
"params": {
"level": "info",
"message": "Elasticsearch query rule '{{rule.name}}' is active:\n- Value: {{context.value}} - Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}} - Timestamp: {{context.date}} - Link: {{context.link}}"
},
"uuid": "bfe370a3-531b-4855-bbe6-ad739f578844"
}
],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "stackAlerts",
"created_at": "2023-11-01T19:00:10.453Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_execution_date": "2023-11-01T19:00:10.453Z",
"status": "pending"
},
"id": "e0d62360-78e8-11ee-9177-f7d404c8c945",
"mute_all": false,
"muted_alert_ids": [],
"name": "my Elasticsearch query ESQL rule",
"notify_when": null,
"params": {
"aggType": "count",
"esqlQuery": {
"esql": "FROM kibana_sample_data_logs | keep bytes, clientip, host, geo.dest | WHERE geo.dest != \"GB\" | stats sumbytes = sum(bytes) by clientip, host | WHERE sumbytes > 5000 | sort sumbytes desc | limit 10"
},
"excludeHitsFromPreviousRun\"": "true,",
"groupBy": "all",
"searchType": "esqlQuery",
"size": 0,
"threshold": [
0
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 1,
"timeWindowUnit": "d"
},
"revision": 0,
"rule_type_id": ".es-query",
"running": false,
"schedule": {
"interval": "1d"
},
"scheduled_task_id": "e0d62360-78e8-11ee-9177-f7d404c8c945",
"tags": [],
"throttle": null,
"updated_at": "2023-11-01T19:00:10.453Z",
"updated_by": "elastic\","
}
},
"createEsQueryKqlRuleResponse": {
"description": "The response for successfully creating an Elasticsearch query rule that uses Kibana query language (KQL).",
"summary": "Elasticsearch query rule (KQL)",
"value": {
"actions": [],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2023-07-14T20:24:50.729Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_execution_date": "2023-07-14T20:24:50.729Z",
"status": "pending"
},
"id": "7bd506d0-2284-11ee-8fad-6101956ced88",
"mute_all": false,
"muted_alert_ids": [],
"name": "my Elasticsearch query KQL rule\"",
"notify_when": null,
"params": {
"aggType": "count",
"excludeHitsFromPreviousRun": true,
"groupBy": "all",
"searchConfiguration": {
"index": "90943e30-9a47-11e8-b64d-95841ca0b247",
"query": {
"language": "kuery",
"query": "\"\"geo.src : \"US\" \"\""
}
},
"searchType": "searchSource",
"size": 100,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"revision": 0,
"rule_type_id": ".es-query",
"running": false,
"schedule": {
"interval": "1m"
},
"scheduled_task_id": "7bd506d0-2284-11ee-8fad-6101956ced88",
"tags": [],
"throttle": null,
"updated_at": "2023-07-14T20:24:50.729Z",
"updated_by": "elastic"
}
},
"createEsQueryRuleResponse": {
"description": "The response for successfully creating an Elasticsearch query rule that uses Elasticsearch query domain specific language (DSL).",
"summary": "Elasticsearch query rule (DSL)",
"value": {
"actions": [
{
"connector_type_id": ".server-log",
"frequency": {
"notify_when": "onThrottleInterval",
"summary": true,
"throttle": "1d"
},
"group": "query matched",
"id": "fdbece50-406c-11ee-850e-c71febc4ca7f",
"params": {
"level": "info",
"message": "The system has detected {{alerts.new.count}} new, {{alerts.ongoing.count}} ongoing, and {{alerts.recovered.count}} recovered alerts."
},
"uuid": "53f3c2a3-e5d0-4cfa-af3b-6f0881385e78"
},
{
"connector_type_id": ".server-log",
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false,
"throttle": null
},
"group": "recovered",
"id": "fdbece50-406c-11ee-850e-c71febc4ca7f",
"params": {
"level": "info",
"message": "Recovered"
},
"uuid": "2324e45b-c0df-45c7-9d70-4993e30be758"
}
],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2023-08-22T00:03:38.263Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_execution_date": "2023-08-22T00:03:38.263Z",
"status": "pending"
},
"id": "58148c70-407f-11ee-850e-c71febc4ca7f",
"mute_all": false,
"muted_alert_ids": [],
"name": "my Elasticsearch query rule",
"notify_when": null,
"params": {
"aggType": "count",
"esQuery": "\"\"\"{\"query\":{\"match_all\" : {}}}\"\"\"",
"excludeHitsFromPreviousRun": true,
"groupBy": "all",
"index": [
"kibana_sample_data_logs"
],
"searchType": "esQuery",
"size": 100,
"threshold": [
100
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 1,
"timeWindowUnit": "d"
},
"revision": 0,
"rule_type_id": ".es-query",
"running": false,
"schedule": {
"interval": "1d"
},
"scheduled_task_id": "58148c70-407f-11ee-850e-c71febc4ca7f",
"tags": [],
"throttle": null,
"updated_at": "2023-08-22T00:03:38.263Z",
"updated_by": "elastic"
}
},
"createIndexThresholdRuleResponse": {
"description": "The response for successfully creating an index threshold rule.",
"summary": "Index threshold rule",
"value": {
"actions": [
{
"connector_type_id": ".server-log",
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false,
"throttle": null
},
"group": "threshold met",
"id": "dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2",
"params": {
"level": "info",
"message": "Rule {{rule.name}} is active for group {{context.group} :\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
},
"uuid": "07aef2a0-9eed-4ef9-94ec-39ba58eb609d"
}
],
"alert_delay": {
"active": 3
},
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2022-06-08T17:20:31.632Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_execution_date": "2022-06-08T17:20:31.632Z",
"status": "pending"
},
"id": "41893910-6bca-11eb-9e0d-85d233e3ee35",
"mute_all": false,
"muted_alert_ids": [],
"name": "my rule",
"notify_when": null,
"params": {
"aggField": "sheet.version",
"aggType": "avg",
"groupBy": "top",
"index": [
".test-index"
],
"termField": "name.keyword",
"termSize": 6,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"revision": 0,
"rule_type_id": ".index-threshold",
"running": false,
"schedule": {
"interval": "1m"
},
"scheduled_task_id": "425b0800-6bca-11eb-9e0d-85d233e3ee35",
"tags": [
"cpu"
],
"throttle": null,
"updated_at": "2022-06-08T17:20:31.632Z",
"updated_by": "elastic"
}
},
"createTrackingContainmentRuleResponse": {
"description": "The response for successfully creating a tracking containment rule.",
"summary": "Tracking containment rule",
"value": {
"actions": [],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2024-02-14T19:52:55.920Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_duration": 74,
"last_execution_date": "2024-02-15T03:25:38.125Z",
"status": "ok"
},
"id": "b6883f9d-5f70-4758-a66e-369d7c26012f",
"last_run": {
"alerts_count": {
"active": 0,
"ignored": 0,
"new": 0,
"recovered": 0
},
"outcome": "succeeded",
"outcome_msg": null,
"outcome_order": 0,
"warning": null
},
"mute_all": false,
"muted_alert_ids": [],
"name": "my tracking rule",
"next_run": "2024-02-15T03:26:38.033Z",
"notify_when": null,
"params": {
"boundaryGeoField": "location",
"boundaryIndexId": "0cd90abf-abe7-44c7-909a-f621bbbcfefc",
"boundaryIndexTitle": "boundary*",
"boundaryNameField": "name",
"boundaryType": "entireIndex",
"dateField": "@timestamp",
"entity": "agent.keyword",
"geoField": "geo.coordinates",
"index": "kibana_sample_data_logs",
"indexId": "90943e30-9a47-11e8-b64d-95841ca0b247"
},
"revision": 1,
"rule_type_id": ".geo-containment",
"running": false,
"schedule": {
"interval": "1h"
},
"scheduled_task_id": "b6883f9d-5f70-4758-a66e-369d7c26012f",
"tags": [],
"throttle": null,
"updated_at": "2024-02-15T03:24:32.574Z",
"updated_by": "elastic"
}
}
},
"schema": {
"additionalProperties": false,
"properties": {
@ -2970,6 +3503,50 @@
"requestBody": {
"content": {
"application/json": {
"examples": {
"updateRuleRequest": {
"description": "Update an index threshold rule that uses a server log connector to send notifications when the threshold is met.",
"summary": "Index threshold rule",
"value": {
"actions": [
{
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false
},
"group": "threshold met",
"id": "96b668d0-a1b6-11ed-afdf-d39a49596974",
"params": {
"level": "info",
"message": "Rule {{rule.name}} is active for group {{context.group}}:\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
}
}
],
"name": "new name",
"params": {
"aggField": "sheet.version",
"aggType": "avg",
"groupBy": "top",
"index": [
".updated-index"
],
"termField": "name.keyword",
"termSize": 6,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"schedule": {
"interval": "1m"
},
"tags": []
}
}
},
"schema": {
"additionalProperties": false,
"properties": {
@ -3249,6 +3826,86 @@
"200": {
"content": {
"application/json": {
"examples": {
"updateRuleResponse": {
"description": "The response for successfully updating an index threshold rule.",
"summary": "Index threshold rule",
"value": {
"actions": [
{
"connector_type_id": ".server-log",
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false,
"throttle": null
},
"group": "threshold met",
"id": "96b668d0-a1b6-11ed-afdf-d39a49596974",
"params": {
"level": "info",
"message": "Rule {{rule.name}} is active for group {{context.group}}:\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}"
},
"uuid": "07aef2a0-9eed-4ef9-94ec-39ba58eb609d"
}
],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2024-03-26T23:13:20.985Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_duration": 52,
"last_execution_date": "2024-03-26T23:22:51.390Z",
"status": "ok"
},
"id": "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74",
"last_run": {
"alerts_count": {
"active": 0,
"ignored": 0,
"new": 0,
"recovered": 0
},
"outcome": "succeeded",
"outcome_msg": null,
"warning": null
},
"mute_all": false,
"muted_alert_ids": [],
"name": "new name",
"next_run": "2024-03-26T23:23:51.316Z",
"params": {
"aggField": "sheet.version",
"aggType": "avg",
"groupBy": "top",
"index": [
".updated-index"
],
"termField": "name.keyword",
"termSize": 6,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"revision": 1,
"rule_type_id": ".index-threshold",
"running": false,
"schedule": {
"interval": "1m"
},
"scheduled_task_id": "4c5eda00-e74f-11ec-b72f-5b18752ff9ea",
"tags": [],
"throttle": null,
"updated_at": "2024-03-26T23:22:59.949Z",
"updated_by": "elastic"
}
}
},
"schema": {
"additionalProperties": false,
"properties": {
@ -4861,6 +5518,257 @@
"200": {
"content": {
"application/json": {
"examples": {
"findConditionalActionRulesResponse": {
"description": "A response that contains information about an index threshold rule.",
"summary": "Index threshold rule",
"value": {
"data": [
{
"actions": [
{
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false,
"throttle": null
},
"group": "threshold met",
"id": "9dca3e00-74f5-11ed-9801-35303b735aef",
"params": {
"connector_type_id": ".server-log",
"level": "info",
"message": "Rule {{rule.name}} is active for group {{context.group}}:\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
},
"uuid": "1c7a1280-f28c-4e06-96b2-e4e5f05d1d61"
}
],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2022-12-05T23:40:33.132Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_duration": 48,
"last_execution_date": "2022-12-06T01:44:23.983Z",
"status": "ok"
},
"id": "3583a470-74f6-11ed-9801-35303b735aef",
"last_run": {
"alerts_count": {
"active": 0,
"ignored": 0,
"new": 0,
"recovered": 0
},
"outcome": "succeeded",
"outcome_msg": null,
"warning": null
},
"mute_all": false,
"muted_alert_ids": [],
"name": "my alert",
"next_run": "2022-12-06T01:45:23.912Z",
"params": {
"aggField": "sheet.version",
"aggType": "avg",
"groupBy": "top",
"index": [
"test-index"
],
"termField": "name.keyword",
"termSize": 6,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"revision": 1,
"rule_type_id": ".index-threshold",
"schedule": {
"interval": "1m"
},
"scheduled_task_id": "3583a470-74f6-11ed-9801-35303b735aef",
"tags": [
"cpu"
],
"throttle": null,
"updated_at": "2022-12-05T23:40:33.132Z",
"updated_by": "elastic"
}
],
"page": 1,
"per_page": 10,
"total": 1
}
},
"findRulesResponse": {
"description": "A response that contains information about a security rule that has conditional actions.",
"summary": "Security rule",
"value": {
"data": [
{
"actions": [
{
"alerts_filter": {
"query": {
"filters": [
{
"$state": {
"store": "appState"
},
"meta": {
"alias": null,
"disabled": false,
"field": "client.geo.region_iso_code",
"index": "c4bdca79-e69e-4d80-82a1-e5192c621bea",
"key": "client.geo.region_iso_code",
"negate": false,
"params": {
"query": "CA-QC",
"type": "phrase"
}
},
"query": {
"match_phrase": {
"client.geo.region_iso_code": "CA-QC"
}
}
}
],
"kql": ""
},
"timeframe": {
"days": [
7
],
"hours": {
"end": "17:00",
"start": "08:00"
},
"timezone": "UTC"
}
},
"connector_type_id": ".index",
"frequency": {
"notify_when": "onActiveAlert",
"summary": true,
"throttle": null
},
"group": "default",
"id": "49eae970-f401-11ed-9f8e-399c75a2deeb",
"params": {
"documents": [
{
"alert_id": {
"[object Object]": null
},
"context_message": {
"[object Object]": null
},
"rule_id": {
"[object Object]": null
},
"rule_name": {
"[object Object]": null
}
}
]
},
"uuid": "1c7a1280-f28c-4e06-96b2-e4e5f05d1d61"
}
],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "siem",
"created_at": "2023-05-16T15:50:28.358Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_duration": 166,
"last_execution_date": "2023-05-16T20:26:49.590Z",
"status": "ok"
},
"id": "6107a8f0-f401-11ed-9f8e-399c75a2deeb",
"last_run": {
"alerts_count": {
"active": 0,
"ignored": 0,
"new": 0,
"recovered": 0
},
"outcome": "succeeded",
"outcome_msg": [
"Rule execution completed successfully"
],
"outcome_order": 0,
"warning": null
},
"mute_all": false,
"muted_alert_ids": [],
"name": "security_rule",
"next_run": "2023-05-16T20:27:49.507Z",
"notify_when": null,
"params": {
"author": [],
"description": "A security threshold rule.",
"exceptionsList": [],
"falsePositives": [],
"filters": [],
"from": "now-3660s",
"immutable": false,
"index": [
"kibana_sample_data_logs"
],
"language": "kuery",
"license": "",
"maxSignals": 100,
"meta": {
"from": "1h",
"kibana_siem_app_url": "https://localhost:5601/app/security"
},
"outputIndex": "",
"query": "*",
"references": [],
"riskScore": 21,
"riskScoreMapping": [],
"ruleId": "an_internal_rule_id",
"severity": "low",
"severityMapping": [],
"threat": [],
"threshold": {
"cardinality": [],
"field": [
"bytes"
],
"value": 1
},
"to": "now",
"type": "threshold",
"version": 1
},
"revision": 1,
"rule_type_id": "siem.thresholdRule",
"running": false,
"schedule": {
"interval": "1m"
},
"scheduled_task_id": "6107a8f0-f401-11ed-9f8e-399c75a2deeb",
"tags": [],
"throttle": null,
"updated_at": "2023-05-16T20:25:42.559Z",
"updated_by": "elastic"
}
],
"page": 1,
"per_page": 10,
"total": 1
}
}
},
"schema": {
"additionalProperties": false,
"properties": {

View file

@ -1777,6 +1777,206 @@
"requestBody": {
"content": {
"application/json": {
"examples": {
"createEsQueryEsqlRuleRequest": {
"description": "Create an Elasticsearch query rule that uses Elasticsearch Query Language (ES|QL) to define its query and a server log connector to send notifications.\n",
"summary": "Elasticsearch query rule (ES|QL)",
"value": {
"actions": [
{
"frequency": {
"notify_when": "onActiveAlert",
"summary": false
},
"group": "query matched",
"id": "d0db1fe0-78d6-11ee-9177-f7d404c8c945",
"params": {
"level": "info",
"message": "Elasticsearch query rule '{{rule.name}}' is active:\n- Value: {{context.value}} - Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}} - Timestamp: {{context.date}} - Link: {{context.link}}"
}
}
],
"consumer": "stackAlerts",
"name": "my Elasticsearch query ESQL rule",
"params": {
"esqlQuery": {
"esql": "FROM kibana_sample_data_logs | KEEP bytes, clientip, host, geo.dest | where geo.dest != \"GB\" | STATS sumbytes = sum(bytes) by clientip, host | WHERE sumbytes > 5000 | SORT sumbytes desc | LIMIT 10"
},
"searchType": "esqlQuery",
"size": 0,
"threshold": [
0
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 1,
"timeWindowUnit": "d"
},
"rule_type_id": ".es-query",
"schedule": {
"interval": "1d"
}
}
},
"createEsQueryKqlRuleRequest": {
"description": "Create an Elasticsearch query rule that uses Kibana query language (KQL).",
"summary": "Elasticsearch query rule (KQL)",
"value": {
"consumer": "alerts",
"name": "my Elasticsearch query KQL rule",
"params": {
"aggType": "count",
"excludeHitsFromPreviousRun": true,
"groupBy": "all",
"searchConfiguration": {
"index": "90943e30-9a47-11e8-b64d-95841ca0b247",
"query": {
"language": "kuery",
"query": "\"\"geo.src : \"US\" \"\""
}
},
"searchType": "searchSource",
"size": 100,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"rule_type_id": ".es-query",
"schedule": {
"interval": "1m"
}
}
},
"createEsQueryRuleRequest": {
"description": "Create an Elasticsearch query rule that uses Elasticsearch query domain specific language (DSL) to define its query and a server log connector to send notifications.\n",
"summary": "Elasticsearch query rule (DSL)",
"value": {
"actions": [
{
"frequency": {
"notify_when": "onThrottleInterval",
"summary": true,
"throttle": "1d"
},
"group": "query matched",
"id": "fdbece50-406c-11ee-850e-c71febc4ca7f",
"params": {
"level": "info",
"message": "The system has detected {{alerts.new.count}} new, {{alerts.ongoing.count}} ongoing, and {{alerts.recovered.count}} recovered alerts."
}
},
{
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false
},
"group": "recovered",
"id": "fdbece50-406c-11ee-850e-c71febc4ca7f",
"params": {
"level": "info",
"message": "Recovered"
}
}
],
"consumer": "alerts",
"name": "my Elasticsearch query rule",
"params": {
"esQuery": "\"\"\"{\"query\":{\"match_all\" : {}}}\"\"\"",
"index": [
"kibana_sample_data_logs"
],
"size": 100,
"threshold": [
100
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 1,
"timeWindowUnit": "d"
},
"rule_type_id": ".es-query",
"schedule": {
"interval": "1d"
}
}
},
"createIndexThresholdRuleRequest": {
"description": "Create an index threshold rule that uses a server log connector to send notifications when the threshold is met.\n",
"summary": "Index threshold rule",
"value": {
"actions": [
{
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false
},
"group": "threshold met",
"id": "48de3460-f401-11ed-9f8e-399c75a2deeb",
"params": {
"level": "info",
"message": "Rule '{{rule.name}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
}
}
],
"alert_delay": {
"active": 3
},
"consumer": "alerts",
"name": "my rule",
"params": {
"aggField": "sheet.version",
"aggType": "avg",
"groupBy": "top",
"index": [
".test-index"
],
"termField": "name.keyword",
"termSize": 6,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"rule_type_id": ".index-threshold",
"schedule": {
"interval": "1m"
},
"tags": [
"cpu"
]
}
},
"createTrackingContainmentRuleRequest": {
"description": "Create a tracking containment rule that checks when an entity is contained or no longer contained within a boundary.\n",
"summary": "Tracking containment rule",
"value": {
"consumer": "alerts",
"name": "my tracking rule",
"params": {
"boundaryGeoField": "location",
"boundaryIndexId": "0cd90abf-abe7-44c7-909a-f621bbbcfefc",
"boundaryIndexTitle": "boundary*",
"boundaryNameField": "name",
"boundaryType": "entireIndex",
"dateField\"": "@timestamp",
"entity": "agent.keyword",
"geoField": "geo.coordinates",
"index": "kibana_sample_data_logs",
"indexId": "90943e30-9a47-11e8-b64d-95841ca0b247"
},
"rule_type_id": ".geo-containment",
"schedule": {
"interval": "1h"
}
}
}
},
"schema": {
"additionalProperties": false,
"properties": {
@ -2073,6 +2273,339 @@
"200": {
"content": {
"application/json": {
"examples": {
"createEsQueryEsqlRuleResponse": {
"description": "The response for successfully creating an Elasticsearch query rule that uses Elasticsearch Query Language (ES|QL).",
"summary": "Elasticsearch query rule (ES|QL)",
"value": {
"actions": [
{
"connector_type_id": ".server-log",
"frequency": {
"notify_when": "onActiveAlert",
"summary": false,
"throttle": null
},
"group": "query matched",
"id": "d0db1fe0-78d6-11ee-9177-f7d404c8c945",
"params": {
"level": "info",
"message": "Elasticsearch query rule '{{rule.name}}' is active:\n- Value: {{context.value}} - Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}} - Timestamp: {{context.date}} - Link: {{context.link}}"
},
"uuid": "bfe370a3-531b-4855-bbe6-ad739f578844"
}
],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "stackAlerts",
"created_at": "2023-11-01T19:00:10.453Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_execution_date": "2023-11-01T19:00:10.453Z",
"status": "pending"
},
"id": "e0d62360-78e8-11ee-9177-f7d404c8c945",
"mute_all": false,
"muted_alert_ids": [],
"name": "my Elasticsearch query ESQL rule",
"notify_when": null,
"params": {
"aggType": "count",
"esqlQuery": {
"esql": "FROM kibana_sample_data_logs | keep bytes, clientip, host, geo.dest | WHERE geo.dest != \"GB\" | stats sumbytes = sum(bytes) by clientip, host | WHERE sumbytes > 5000 | sort sumbytes desc | limit 10"
},
"excludeHitsFromPreviousRun\"": "true,",
"groupBy": "all",
"searchType": "esqlQuery",
"size": 0,
"threshold": [
0
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 1,
"timeWindowUnit": "d"
},
"revision": 0,
"rule_type_id": ".es-query",
"running": false,
"schedule": {
"interval": "1d"
},
"scheduled_task_id": "e0d62360-78e8-11ee-9177-f7d404c8c945",
"tags": [],
"throttle": null,
"updated_at": "2023-11-01T19:00:10.453Z",
"updated_by": "elastic\","
}
},
"createEsQueryKqlRuleResponse": {
"description": "The response for successfully creating an Elasticsearch query rule that uses Kibana query language (KQL).",
"summary": "Elasticsearch query rule (KQL)",
"value": {
"actions": [],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2023-07-14T20:24:50.729Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_execution_date": "2023-07-14T20:24:50.729Z",
"status": "pending"
},
"id": "7bd506d0-2284-11ee-8fad-6101956ced88",
"mute_all": false,
"muted_alert_ids": [],
"name": "my Elasticsearch query KQL rule\"",
"notify_when": null,
"params": {
"aggType": "count",
"excludeHitsFromPreviousRun": true,
"groupBy": "all",
"searchConfiguration": {
"index": "90943e30-9a47-11e8-b64d-95841ca0b247",
"query": {
"language": "kuery",
"query": "\"\"geo.src : \"US\" \"\""
}
},
"searchType": "searchSource",
"size": 100,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"revision": 0,
"rule_type_id": ".es-query",
"running": false,
"schedule": {
"interval": "1m"
},
"scheduled_task_id": "7bd506d0-2284-11ee-8fad-6101956ced88",
"tags": [],
"throttle": null,
"updated_at": "2023-07-14T20:24:50.729Z",
"updated_by": "elastic"
}
},
"createEsQueryRuleResponse": {
"description": "The response for successfully creating an Elasticsearch query rule that uses Elasticsearch query domain specific language (DSL).",
"summary": "Elasticsearch query rule (DSL)",
"value": {
"actions": [
{
"connector_type_id": ".server-log",
"frequency": {
"notify_when": "onThrottleInterval",
"summary": true,
"throttle": "1d"
},
"group": "query matched",
"id": "fdbece50-406c-11ee-850e-c71febc4ca7f",
"params": {
"level": "info",
"message": "The system has detected {{alerts.new.count}} new, {{alerts.ongoing.count}} ongoing, and {{alerts.recovered.count}} recovered alerts."
},
"uuid": "53f3c2a3-e5d0-4cfa-af3b-6f0881385e78"
},
{
"connector_type_id": ".server-log",
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false,
"throttle": null
},
"group": "recovered",
"id": "fdbece50-406c-11ee-850e-c71febc4ca7f",
"params": {
"level": "info",
"message": "Recovered"
},
"uuid": "2324e45b-c0df-45c7-9d70-4993e30be758"
}
],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2023-08-22T00:03:38.263Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_execution_date": "2023-08-22T00:03:38.263Z",
"status": "pending"
},
"id": "58148c70-407f-11ee-850e-c71febc4ca7f",
"mute_all": false,
"muted_alert_ids": [],
"name": "my Elasticsearch query rule",
"notify_when": null,
"params": {
"aggType": "count",
"esQuery": "\"\"\"{\"query\":{\"match_all\" : {}}}\"\"\"",
"excludeHitsFromPreviousRun": true,
"groupBy": "all",
"index": [
"kibana_sample_data_logs"
],
"searchType": "esQuery",
"size": 100,
"threshold": [
100
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 1,
"timeWindowUnit": "d"
},
"revision": 0,
"rule_type_id": ".es-query",
"running": false,
"schedule": {
"interval": "1d"
},
"scheduled_task_id": "58148c70-407f-11ee-850e-c71febc4ca7f",
"tags": [],
"throttle": null,
"updated_at": "2023-08-22T00:03:38.263Z",
"updated_by": "elastic"
}
},
"createIndexThresholdRuleResponse": {
"description": "The response for successfully creating an index threshold rule.",
"summary": "Index threshold rule",
"value": {
"actions": [
{
"connector_type_id": ".server-log",
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false,
"throttle": null
},
"group": "threshold met",
"id": "dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2",
"params": {
"level": "info",
"message": "Rule {{rule.name}} is active for group {{context.group} :\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
},
"uuid": "07aef2a0-9eed-4ef9-94ec-39ba58eb609d"
}
],
"alert_delay": {
"active": 3
},
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2022-06-08T17:20:31.632Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_execution_date": "2022-06-08T17:20:31.632Z",
"status": "pending"
},
"id": "41893910-6bca-11eb-9e0d-85d233e3ee35",
"mute_all": false,
"muted_alert_ids": [],
"name": "my rule",
"notify_when": null,
"params": {
"aggField": "sheet.version",
"aggType": "avg",
"groupBy": "top",
"index": [
".test-index"
],
"termField": "name.keyword",
"termSize": 6,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"revision": 0,
"rule_type_id": ".index-threshold",
"running": false,
"schedule": {
"interval": "1m"
},
"scheduled_task_id": "425b0800-6bca-11eb-9e0d-85d233e3ee35",
"tags": [
"cpu"
],
"throttle": null,
"updated_at": "2022-06-08T17:20:31.632Z",
"updated_by": "elastic"
}
},
"createTrackingContainmentRuleResponse": {
"description": "The response for successfully creating a tracking containment rule.",
"summary": "Tracking containment rule",
"value": {
"actions": [],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2024-02-14T19:52:55.920Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_duration": 74,
"last_execution_date": "2024-02-15T03:25:38.125Z",
"status": "ok"
},
"id": "b6883f9d-5f70-4758-a66e-369d7c26012f",
"last_run": {
"alerts_count": {
"active": 0,
"ignored": 0,
"new": 0,
"recovered": 0
},
"outcome": "succeeded",
"outcome_msg": null,
"outcome_order": 0,
"warning": null
},
"mute_all": false,
"muted_alert_ids": [],
"name": "my tracking rule",
"next_run": "2024-02-15T03:26:38.033Z",
"notify_when": null,
"params": {
"boundaryGeoField": "location",
"boundaryIndexId": "0cd90abf-abe7-44c7-909a-f621bbbcfefc",
"boundaryIndexTitle": "boundary*",
"boundaryNameField": "name",
"boundaryType": "entireIndex",
"dateField": "@timestamp",
"entity": "agent.keyword",
"geoField": "geo.coordinates",
"index": "kibana_sample_data_logs",
"indexId": "90943e30-9a47-11e8-b64d-95841ca0b247"
},
"revision": 1,
"rule_type_id": ".geo-containment",
"running": false,
"schedule": {
"interval": "1h"
},
"scheduled_task_id": "b6883f9d-5f70-4758-a66e-369d7c26012f",
"tags": [],
"throttle": null,
"updated_at": "2024-02-15T03:24:32.574Z",
"updated_by": "elastic"
}
}
},
"schema": {
"additionalProperties": false,
"properties": {
@ -2970,6 +3503,50 @@
"requestBody": {
"content": {
"application/json": {
"examples": {
"updateRuleRequest": {
"description": "Update an index threshold rule that uses a server log connector to send notifications when the threshold is met.",
"summary": "Index threshold rule",
"value": {
"actions": [
{
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false
},
"group": "threshold met",
"id": "96b668d0-a1b6-11ed-afdf-d39a49596974",
"params": {
"level": "info",
"message": "Rule {{rule.name}} is active for group {{context.group}}:\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
}
}
],
"name": "new name",
"params": {
"aggField": "sheet.version",
"aggType": "avg",
"groupBy": "top",
"index": [
".updated-index"
],
"termField": "name.keyword",
"termSize": 6,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"schedule": {
"interval": "1m"
},
"tags": []
}
}
},
"schema": {
"additionalProperties": false,
"properties": {
@ -3249,6 +3826,86 @@
"200": {
"content": {
"application/json": {
"examples": {
"updateRuleResponse": {
"description": "The response for successfully updating an index threshold rule.",
"summary": "Index threshold rule",
"value": {
"actions": [
{
"connector_type_id": ".server-log",
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false,
"throttle": null
},
"group": "threshold met",
"id": "96b668d0-a1b6-11ed-afdf-d39a49596974",
"params": {
"level": "info",
"message": "Rule {{rule.name}} is active for group {{context.group}}:\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}"
},
"uuid": "07aef2a0-9eed-4ef9-94ec-39ba58eb609d"
}
],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2024-03-26T23:13:20.985Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_duration": 52,
"last_execution_date": "2024-03-26T23:22:51.390Z",
"status": "ok"
},
"id": "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74",
"last_run": {
"alerts_count": {
"active": 0,
"ignored": 0,
"new": 0,
"recovered": 0
},
"outcome": "succeeded",
"outcome_msg": null,
"warning": null
},
"mute_all": false,
"muted_alert_ids": [],
"name": "new name",
"next_run": "2024-03-26T23:23:51.316Z",
"params": {
"aggField": "sheet.version",
"aggType": "avg",
"groupBy": "top",
"index": [
".updated-index"
],
"termField": "name.keyword",
"termSize": 6,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"revision": 1,
"rule_type_id": ".index-threshold",
"running": false,
"schedule": {
"interval": "1m"
},
"scheduled_task_id": "4c5eda00-e74f-11ec-b72f-5b18752ff9ea",
"tags": [],
"throttle": null,
"updated_at": "2024-03-26T23:22:59.949Z",
"updated_by": "elastic"
}
}
},
"schema": {
"additionalProperties": false,
"properties": {
@ -4861,6 +5518,257 @@
"200": {
"content": {
"application/json": {
"examples": {
"findConditionalActionRulesResponse": {
"description": "A response that contains information about an index threshold rule.",
"summary": "Index threshold rule",
"value": {
"data": [
{
"actions": [
{
"frequency": {
"notify_when": "onActionGroupChange",
"summary": false,
"throttle": null
},
"group": "threshold met",
"id": "9dca3e00-74f5-11ed-9801-35303b735aef",
"params": {
"connector_type_id": ".server-log",
"level": "info",
"message": "Rule {{rule.name}} is active for group {{context.group}}:\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
},
"uuid": "1c7a1280-f28c-4e06-96b2-e4e5f05d1d61"
}
],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "alerts",
"created_at": "2022-12-05T23:40:33.132Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_duration": 48,
"last_execution_date": "2022-12-06T01:44:23.983Z",
"status": "ok"
},
"id": "3583a470-74f6-11ed-9801-35303b735aef",
"last_run": {
"alerts_count": {
"active": 0,
"ignored": 0,
"new": 0,
"recovered": 0
},
"outcome": "succeeded",
"outcome_msg": null,
"warning": null
},
"mute_all": false,
"muted_alert_ids": [],
"name": "my alert",
"next_run": "2022-12-06T01:45:23.912Z",
"params": {
"aggField": "sheet.version",
"aggType": "avg",
"groupBy": "top",
"index": [
"test-index"
],
"termField": "name.keyword",
"termSize": 6,
"threshold": [
1000
],
"thresholdComparator": ">",
"timeField": "@timestamp",
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"revision": 1,
"rule_type_id": ".index-threshold",
"schedule": {
"interval": "1m"
},
"scheduled_task_id": "3583a470-74f6-11ed-9801-35303b735aef",
"tags": [
"cpu"
],
"throttle": null,
"updated_at": "2022-12-05T23:40:33.132Z",
"updated_by": "elastic"
}
],
"page": 1,
"per_page": 10,
"total": 1
}
},
"findRulesResponse": {
"description": "A response that contains information about a security rule that has conditional actions.",
"summary": "Security rule",
"value": {
"data": [
{
"actions": [
{
"alerts_filter": {
"query": {
"filters": [
{
"$state": {
"store": "appState"
},
"meta": {
"alias": null,
"disabled": false,
"field": "client.geo.region_iso_code",
"index": "c4bdca79-e69e-4d80-82a1-e5192c621bea",
"key": "client.geo.region_iso_code",
"negate": false,
"params": {
"query": "CA-QC",
"type": "phrase"
}
},
"query": {
"match_phrase": {
"client.geo.region_iso_code": "CA-QC"
}
}
}
],
"kql": ""
},
"timeframe": {
"days": [
7
],
"hours": {
"end": "17:00",
"start": "08:00"
},
"timezone": "UTC"
}
},
"connector_type_id": ".index",
"frequency": {
"notify_when": "onActiveAlert",
"summary": true,
"throttle": null
},
"group": "default",
"id": "49eae970-f401-11ed-9f8e-399c75a2deeb",
"params": {
"documents": [
{
"alert_id": {
"[object Object]": null
},
"context_message": {
"[object Object]": null
},
"rule_id": {
"[object Object]": null
},
"rule_name": {
"[object Object]": null
}
}
]
},
"uuid": "1c7a1280-f28c-4e06-96b2-e4e5f05d1d61"
}
],
"api_key_created_by_user": false,
"api_key_owner": "elastic",
"consumer": "siem",
"created_at": "2023-05-16T15:50:28.358Z",
"created_by": "elastic",
"enabled": true,
"execution_status": {
"last_duration": 166,
"last_execution_date": "2023-05-16T20:26:49.590Z",
"status": "ok"
},
"id": "6107a8f0-f401-11ed-9f8e-399c75a2deeb",
"last_run": {
"alerts_count": {
"active": 0,
"ignored": 0,
"new": 0,
"recovered": 0
},
"outcome": "succeeded",
"outcome_msg": [
"Rule execution completed successfully"
],
"outcome_order": 0,
"warning": null
},
"mute_all": false,
"muted_alert_ids": [],
"name": "security_rule",
"next_run": "2023-05-16T20:27:49.507Z",
"notify_when": null,
"params": {
"author": [],
"description": "A security threshold rule.",
"exceptionsList": [],
"falsePositives": [],
"filters": [],
"from": "now-3660s",
"immutable": false,
"index": [
"kibana_sample_data_logs"
],
"language": "kuery",
"license": "",
"maxSignals": 100,
"meta": {
"from": "1h",
"kibana_siem_app_url": "https://localhost:5601/app/security"
},
"outputIndex": "",
"query": "*",
"references": [],
"riskScore": 21,
"riskScoreMapping": [],
"ruleId": "an_internal_rule_id",
"severity": "low",
"severityMapping": [],
"threat": [],
"threshold": {
"cardinality": [],
"field": [
"bytes"
],
"value": 1
},
"to": "now",
"type": "threshold",
"version": 1
},
"revision": 1,
"rule_type_id": "siem.thresholdRule",
"running": false,
"schedule": {
"interval": "1m"
},
"scheduled_task_id": "6107a8f0-f401-11ed-9f8e-399c75a2deeb",
"tags": [],
"throttle": null,
"updated_at": "2023-05-16T20:25:42.559Z",
"updated_by": "elastic"
}
],
"page": 1,
"per_page": 10,
"total": 1
}
}
},
"schema": {
"additionalProperties": false,
"properties": {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -4,67 +4,6 @@ info:
title: Overlays for the alerting rule examples and parameters
version: 0.0.1
actions:
# Add some alerting API examples
- target: "$.paths['/api/alerting/rule/{id}']['post']"
description: "Add examples to create rule API"
update:
requestBody:
content:
application/json:
examples:
createEsQueryEsqlRuleRequest:
$ref: "../examples/create_es_query_esql_rule_request.yaml"
createEsQueryRuleRequest:
$ref: "../examples/create_es_query_rule_request.yaml"
createEsQueryKqlRuleRequest:
$ref: "../examples/create_es_query_kql_rule_request.yaml"
createIndexThresholdRuleRequest:
$ref: "../examples/create_index_threshold_rule_request.yaml"
createTrackingContainmentRuleRequest:
$ref: "../examples/create_tracking_containment_rule_request.yaml"
responses:
200:
content:
application/json:
examples:
createEsQueryEsqlRuleResponse:
$ref: "../examples/create_es_query_esql_rule_response.yaml"
createEsQueryRuleResponse:
$ref: "../examples/create_es_query_rule_response.yaml"
createEsQueryKqlRuleResponse:
$ref: "../examples/create_es_query_kql_rule_response.yaml"
createIndexThresholdRuleResponse:
$ref: "../examples/create_index_threshold_rule_response.yaml"
createTrackingContainmentRuleResponse:
$ref: "../examples/create_tracking_containment_rule_response.yaml"
- target: "$.paths['/api/alerting/rule/{id}']['put']"
description: "Add examples to update rule API"
update:
requestBody:
content:
application/json:
examples:
updateRuleRequest:
$ref: '../examples/update_rule_request.yaml'
responses:
200:
content:
application/json:
examples:
updateRuleResponse:
$ref: '../examples/update_rule_response.yaml'
- target: "$.paths['/api/alerting/rules/_find']['get']"
description: "Add examples to find rules API"
update:
responses:
200:
content:
application/json:
examples:
findRulesResponse:
$ref: '../examples/find_rules_response.yaml'
findConditionalActionRulesResponse:
$ref: '../examples/find_rules_response_conditional_action.yaml'
# Fix some optional path parameters
- target: "$.paths['/api/alerting/rule/{id}']['post'].parameters[?(@.name=='id')]"
description: Remove optional indicator from path parameter

View file

@ -103,6 +103,7 @@
"globby/fast-glob": "^3.3.2"
},
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.9.1",
"@appland/sql-parser": "^1.5.1",
"@aws-crypto/sha256-js": "^5.2.0",
"@aws-crypto/util": "^5.2.0",

View file

@ -611,6 +611,7 @@
"fp-ts",
"getos",
"joi-to-json",
"@apidevtools/json-schema-ref-parser",
"json5",
"load-json-file",
"mock-fs",

View file

@ -304,7 +304,7 @@ export class HttpService
mergeMap(async () => {
try {
// Potentially quite expensive
const result = generateOpenApiDocument(
const result = await generateOpenApiDocument(
this.httpServer.getRouters({ pluginId: query.pluginId }),
{
baseUrl,

View file

@ -7,6 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import type { OpenAPIV3 } from 'openapi-types';
import type { DeepPartial } from '@kbn/utility-types';
import type { RouteValidator } from './route_validator';
/**
@ -387,6 +389,37 @@ export interface RouteConfigOptions<Method extends RouteMethod> {
*/
deprecated?: RouteDeprecationInfo;
/**
* Filepath to a YAML file or a partial {@link OpenAPIV3.OperationObject} to
* be merged with the code generated for this endpoint.
*
* @note Always instantiate the objects lazily to avoid unnecessarily loading
* objects into memory.
*
* @example
* As a path to a OAS file
* () => path.join(__dirname, 'my_examples.yaml')
*
* @example
* As an object
* () => ({
* requestBody: {
* content: {
* 200: {
* examples: {
* fooExample: {
* value: { coolType: true } as { coolType: true },
* },
* },
* },
* },
* },
* })
*/
oasOperationObject?: () =>
| string
| DeepPartial<Pick<OpenAPIV3.OperationObject, 'requestBody' | 'responses'>>;
/**
* Whether this route should be treated as "invisible" and excluded from router
* OAS introspection.

View file

@ -34,9 +34,17 @@ export type VersionedRouteConfig<Method extends RouteMethod> = Omit<
RouteConfig<unknown, unknown, unknown, Method>,
'validate' | 'options'
> & {
options?: Omit<
options?: Pick<
RouteConfigOptions<Method>,
'access' | 'description' | 'summary' | 'deprecated' | 'discontinued'
| 'authRequired'
| 'xsrfRequired'
| 'tags'
| 'body'
| 'timeout'
| 'excludeFromOAS'
| 'excludeFromRateLimiter'
| 'httpResource'
| 'availability'
>;
/** See {@link RouteConfigOptions<RouteMethod>['access']} */
access: Exclude<RouteConfigOptions<Method>['access'], undefined>;
@ -342,6 +350,11 @@ export interface AddVersionOpts<P, Q, B> {
options?: {
deprecated?: RouteDeprecationInfo;
/**
* @public
* {@inheritdoc RouteConfigOptions['oasOperationObject']}
*/
oasOperationObject?: RouteConfigOptions<RouteMethod>['oasOperationObject'];
};
}

View file

@ -8,6 +8,7 @@
*/
import { schema, Type } from '@kbn/config-schema';
import { get } from 'lodash';
import { generateOpenApiDocument } from './generate_oas';
import { createTestRouters, createRouter, createVersionedRouter } from './generate_oas.test.util';
import {
@ -23,7 +24,7 @@ interface RecursiveType {
describe('generateOpenApiDocument', () => {
describe('@kbn/config-schema', () => {
it('generates the expected OpenAPI document for the shared schema', () => {
it('generates the expected OpenAPI document for the shared schema', async () => {
const [routers, versionedRouters] = createTestRouters({
routers: {
testRouter: {
@ -34,7 +35,7 @@ describe('generateOpenApiDocument', () => {
bodySchema: createSharedConfigSchema(),
});
expect(
generateOpenApiDocument(
await generateOpenApiDocument(
{
routers,
versionedRouters,
@ -48,7 +49,7 @@ describe('generateOpenApiDocument', () => {
).toEqual(sharedOas);
});
it('generates the expected OpenAPI document', () => {
it('generates the expected OpenAPI document', async () => {
const [routers, versionedRouters] = createTestRouters({
routers: {
testRouter: {
@ -80,7 +81,7 @@ describe('generateOpenApiDocument', () => {
},
});
expect(
generateOpenApiDocument(
await generateOpenApiDocument(
{
routers,
versionedRouters,
@ -94,7 +95,7 @@ describe('generateOpenApiDocument', () => {
).toMatchSnapshot();
});
it('generates references in the expected format', () => {
it('generates references in the expected format', async () => {
const sharedIdSchema = schema.string({ minLength: 1, meta: { description: 'test' } });
const sharedNameSchema = schema.string({ minLength: 1 });
const otherSchema = schema.object(
@ -102,7 +103,7 @@ describe('generateOpenApiDocument', () => {
{ meta: { id: 'foo' } }
);
expect(
generateOpenApiDocument(
await generateOpenApiDocument(
{
routers: [
createRouter({
@ -139,7 +140,7 @@ describe('generateOpenApiDocument', () => {
).toMatchSnapshot();
});
it('handles recursive schemas', () => {
it('handles recursive schemas', async () => {
const id = 'recursive';
const recursiveSchema: Type<RecursiveType> = schema.object(
{
@ -149,7 +150,7 @@ describe('generateOpenApiDocument', () => {
{ meta: { id } }
);
expect(
generateOpenApiDocument(
await generateOpenApiDocument(
{
routers: [
createRouter({
@ -187,7 +188,7 @@ describe('generateOpenApiDocument', () => {
});
describe('Zod', () => {
it('generates the expected OpenAPI document for the shared schema', () => {
it('generates the expected OpenAPI document for the shared schema', async () => {
const [routers, versionedRouters] = createTestRouters({
routers: { testRouter: { routes: [{ method: 'get' }, { method: 'post' }] } },
versionedRouters: { testVersionedRouter: { routes: [{}] } },
@ -195,7 +196,7 @@ describe('generateOpenApiDocument', () => {
});
expect(
generateOpenApiDocument(
await generateOpenApiDocument(
{
routers,
versionedRouters,
@ -211,9 +212,9 @@ describe('generateOpenApiDocument', () => {
});
describe('unknown schema/validation', () => {
it('produces the expected output', () => {
it('produces the expected output', async () => {
expect(
generateOpenApiDocument(
await generateOpenApiDocument(
{
routers: [
createRouter({
@ -275,7 +276,7 @@ describe('generateOpenApiDocument', () => {
});
describe('tags', () => {
it('handles tags as expected', () => {
it('handles tags as expected', async () => {
const [routers, versionedRouters] = createTestRouters({
routers: {
testRouter1: {
@ -314,7 +315,7 @@ describe('generateOpenApiDocument', () => {
},
},
});
const result = generateOpenApiDocument(
const result = await generateOpenApiDocument(
{
routers,
versionedRouters,
@ -337,7 +338,7 @@ describe('generateOpenApiDocument', () => {
});
describe('availability', () => {
it('creates the expected availability entries', () => {
it('creates the expected availability entries', async () => {
const [routers, versionedRouters] = createTestRouters({
routers: {
testRouter1: {
@ -391,7 +392,7 @@ describe('generateOpenApiDocument', () => {
},
},
});
const result = generateOpenApiDocument(
const result = await generateOpenApiDocument(
{
routers,
versionedRouters,
@ -434,4 +435,84 @@ describe('generateOpenApiDocument', () => {
});
});
});
it('merges operation objects', async () => {
const oasOperationObject = () => ({
requestBody: {
content: {
'application/json': {
examples: {
fooExample: {
value: 999,
},
},
},
},
},
});
const [routers, versionedRouters] = createTestRouters({
routers: {
testRouter: {
routes: [
{
method: 'get',
options: {
access: 'public',
oasOperationObject,
},
},
{ method: 'post' },
],
},
},
versionedRouters: {
testVersionedRouter: {
routes: [
{
options: {
access: 'public',
},
},
],
},
},
bodySchema: createSharedConfigSchema(),
});
versionedRouters[0].getRoutes()[0].handlers[0].options!.options!.oasOperationObject =
oasOperationObject;
const oas = await generateOpenApiDocument(
{ routers, versionedRouters },
{
title: 'test',
baseUrl: 'https://test.oas',
version: '99.99.99',
}
);
expect(
get(oas, [
'paths',
'/foo/{id}/{path}',
'get',
'requestBody',
'content',
'application/json',
'examples',
])
).toEqual({
fooExample: {
value: 999,
},
});
expect(
get(oas, ['paths', '/bar', 'get', 'requestBody', 'content', 'application/json', 'examples'])
).toEqual({
fooExample: {
value: 999,
},
});
});
});

View file

@ -39,10 +39,10 @@ export interface GenerateOpenApiDocumentOptions {
filters?: GenerateOpenApiDocumentOptionsFilters;
}
export const generateOpenApiDocument = (
export const generateOpenApiDocument = async (
appRouters: { routers: Router[]; versionedRouters: CoreVersionedRouter[] },
opts: GenerateOpenApiDocumentOptions
): OpenAPIV3.Document => {
): Promise<OpenAPIV3.Document> => {
let { filters = { access: 'public' } } = opts;
if (filters.access === 'public' && !filters.version) {
filters = { ...filters, version: SERVERLESS_VERSION_2023_10_31 };
@ -51,11 +51,11 @@ export const generateOpenApiDocument = (
const paths: OpenAPIV3.PathsObject = {};
const getOpId = createOpIdGenerator();
for (const router of appRouters.routers) {
const result = processRouter(router, converter, getOpId, filters);
const result = await processRouter(router, converter, getOpId, filters);
Object.assign(paths, result.paths);
}
for (const router of appRouters.versionedRouters) {
const result = processVersionedRouter(router, converter, getOpId, filters);
const result = await processVersionedRouter(router, converter, getOpId, filters);
Object.assign(paths, result.paths);
}
const tags = buildGlobalTags(paths, opts.tags);

View file

@ -0,0 +1,25 @@
/*
* 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".
*/
import type { OpenAPIV3 } from 'openapi-types';
import type { DeepPartial } from '@kbn/utility-types';
import { dereference } from '@apidevtools/json-schema-ref-parser';
import deepMerge from 'deepmerge';
import type { CustomOperationObject } from './type';
export async function mergeOperation(
pathToSpecOrSpec: string | DeepPartial<OpenAPIV3.OperationObject>,
operation: CustomOperationObject
) {
if (typeof pathToSpecOrSpec === 'string') {
Object.assign(operation, deepMerge(operation, await dereference(pathToSpecOrSpec)));
} else {
Object.assign(operation, deepMerge(operation, pathToSpecOrSpec));
}
}

View file

@ -148,23 +148,23 @@ describe('processRouter', () => {
],
} as unknown as Router;
it('only provides routes for version 2023-10-31', () => {
const result1 = processRouter(testRouter, new OasConverter(), createOpIdGenerator(), {
it('only provides routes for version 2023-10-31', async () => {
const result1 = await processRouter(testRouter, new OasConverter(), createOpIdGenerator(), {
version: '2023-10-31',
access: 'public',
});
expect(Object.keys(result1.paths!)).toHaveLength(5);
const result2 = processRouter(testRouter, new OasConverter(), createOpIdGenerator(), {
const result2 = await processRouter(testRouter, new OasConverter(), createOpIdGenerator(), {
version: '2024-10-31',
access: 'public',
});
expect(Object.keys(result2.paths!)).toHaveLength(0);
});
it('updates description with privileges required', () => {
const result = processRouter(testRouter, new OasConverter(), createOpIdGenerator(), {
it('updates description with privileges required', async () => {
const result = await processRouter(testRouter, new OasConverter(), createOpIdGenerator(), {
version: '2023-10-31',
access: 'public',
});

View file

@ -28,8 +28,9 @@ import {
import type { GenerateOpenApiDocumentOptionsFilters } from './generate_oas';
import type { CustomOperationObject, InternalRouterRoute } from './type';
import { extractAuthzDescription } from './extract_authz_description';
import { mergeOperation } from './merge_operation';
export const processRouter = (
export const processRouter = async (
appRouter: Router,
converter: OasConverter,
getOpId: GetOpId,
@ -99,6 +100,10 @@ export const processRouter = (
setXState(route.options.availability, operation);
if (route.options.oasOperationObject) {
await mergeOperation(route.options.oasOperationObject(), operation);
}
const path: OpenAPIV3.PathItemObject = {
[route.method]: operation,
};

View file

@ -121,8 +121,8 @@ describe('extractVersionedResponses', () => {
});
describe('processVersionedRouter', () => {
it('correctly extracts the version based on the version filter', () => {
const baseCase = processVersionedRouter(
it('correctly extracts the version based on the version filter', async () => {
const baseCase = await processVersionedRouter(
{ getRoutes: () => [createTestRoute()] } as unknown as CoreVersionedRouter,
new OasConverter(),
createOpIdGenerator(),
@ -133,7 +133,7 @@ describe('processVersionedRouter', () => {
'application/test+json',
]);
const filteredCase = processVersionedRouter(
const filteredCase = await processVersionedRouter(
{ getRoutes: () => [createTestRoute()] } as unknown as CoreVersionedRouter,
new OasConverter(),
createOpIdGenerator(),
@ -144,8 +144,8 @@ describe('processVersionedRouter', () => {
]);
});
it('correctly updates the authz description for routes that require privileges', () => {
const results = processVersionedRouter(
it('correctly updates the authz description for routes that require privileges', async () => {
const results = await processVersionedRouter(
{ getRoutes: () => [createTestRoute()] } as unknown as CoreVersionedRouter,
new OasConverter(),
createOpIdGenerator(),

View file

@ -31,8 +31,9 @@ import {
GetOpId,
} from './util';
import { isReferenceObject } from './oas_converter/common';
import { mergeOperation } from './merge_operation';
export const processVersionedRouter = (
export const processVersionedRouter = async (
appRouter: CoreVersionedRouter,
converter: OasConverter,
getOpId: GetOpId,
@ -130,6 +131,10 @@ export const processVersionedRouter = (
setXState(route.options.options?.availability, operation);
if (handler.options.options?.oasOperationObject) {
await mergeOperation(handler.options.options.oasOperationObject(), operation);
}
const path: OpenAPIV3.PathItemObject = {
[route.method]: operation,
};

View file

@ -19,5 +19,6 @@
"@kbn/config-schema",
"@kbn/zod",
"@kbn/zod-helpers",
"@kbn/utility-types",
]
}

View file

@ -203,7 +203,6 @@ export function registerRoutes<TDependencies extends Record<string, any>>({
access,
summary: options.summary,
description: options.description,
// @ts-expect-error we are essentially calling multiple methods at the same type so TS gets confused
options: omit(options, 'access', 'description', 'summary', 'deprecated', 'discontinued'),
security,
}).addVersion(

View file

@ -0,0 +1,34 @@
summary: Elasticsearch query rule (ES|QL)
description: >
Create an Elasticsearch query rule that uses Elasticsearch Query Language (ES|QL) to define its query and a server log connector to send notifications.
value:
name: my Elasticsearch query ESQL rule
params:
searchType: esqlQuery
esqlQuery:
esql: 'FROM kibana_sample_data_logs | KEEP bytes, clientip, host, geo.dest | where geo.dest != "GB" | STATS sumbytes = sum(bytes) by clientip, host | WHERE sumbytes > 5000 | SORT sumbytes desc | LIMIT 10'
timeField: "@timestamp"
timeWindowSize: 1
timeWindowUnit: d
size: 0
thresholdComparator: ">"
threshold:
- 0
consumer: stackAlerts
rule_type_id: .es-query
schedule:
interval: 1d
actions:
- group: query matched
id: d0db1fe0-78d6-11ee-9177-f7d404c8c945
params:
level: info
message: "Elasticsearch query rule '{{rule.name}}' is active:
- Value: {{context.value}}
- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}
- Timestamp: {{context.date}}
- Link: {{context.link}}"
frequency:
summary: false
notify_when: onActiveAlert

View file

@ -0,0 +1,58 @@
summary: Elasticsearch query rule (ES|QL)
description: The response for successfully creating an Elasticsearch query rule that uses Elasticsearch Query Language (ES|QL).
value:
id: e0d62360-78e8-11ee-9177-f7d404c8c945
enabled: true
name: my Elasticsearch query ESQL rule
tags: []
rule_type_id: .es-query
consumer: stackAlerts
schedule:
interval: 1d
actions:
- group: query matched
id: d0db1fe0-78d6-11ee-9177-f7d404c8c945
params:
level: info
message: "Elasticsearch query rule '{{rule.name}}' is active:
- Value: {{context.value}}
- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}
- Timestamp: {{context.date}}
- Link: {{context.link}}"
connector_type_id: .server-log
frequency:
summary: false
notify_when: onActiveAlert
throttle: null
uuid: bfe370a3-531b-4855-bbe6-ad739f578844
params:
searchType: esqlQuery
esqlQuery:
esql: 'FROM kibana_sample_data_logs | keep bytes, clientip, host, geo.dest | WHERE geo.dest != "GB" | stats sumbytes = sum(bytes) by clientip, host | WHERE sumbytes > 5000 | sort sumbytes desc | limit 10'
timeField: "@timestamp"
timeWindowSize: 1
timeWindowUnit: d
size: 0
thresholdComparator: ">"
threshold:
- 0
excludeHitsFromPreviousRun": true,
aggType: count
groupBy: all
scheduled_task_id: e0d62360-78e8-11ee-9177-f7d404c8c945
created_by: elastic
updated_by: elastic",
created_at: '2023-11-01T19:00:10.453Z'
updated_at: '2023-11-01T19:00:10.453Z'
api_key_owner: elastic
api_key_created_by_user: false
throttle: null
mute_all: false
notify_when: null
muted_alert_ids: []
execution_status:
status: pending
last_execution_date: '2023-11-01T19:00:10.453Z'
revision: 0
running: false

View file

@ -0,0 +1,24 @@
summary: Elasticsearch query rule (KQL)
description: Create an Elasticsearch query rule that uses Kibana query language (KQL).
value:
consumer: alerts
name: my Elasticsearch query KQL rule
params:
aggType: count
excludeHitsFromPreviousRun: true
groupBy: all
searchConfiguration:
query:
query: '""geo.src : "US" ""'
language: kuery
index: 90943e30-9a47-11e8-b64d-95841ca0b247
searchType: searchSource
size: 100
threshold:
- 1000
thresholdComparator: ">"
timeWindowSize: 5
timeWindowUnit: m
rule_type_id: .es-query
schedule:
interval: 1m

View file

@ -0,0 +1,44 @@
summary: Elasticsearch query rule (KQL)
description: The response for successfully creating an Elasticsearch query rule that uses Kibana query language (KQL).
value:
id: 7bd506d0-2284-11ee-8fad-6101956ced88
enabled: true
name: my Elasticsearch query KQL rule"
tags: []
rule_type_id: .es-query
consumer: alerts
schedule:
interval: 1m
actions: []
params:
searchConfiguration:
query:
query: '""geo.src : "US" ""'
language: kuery
index: 90943e30-9a47-11e8-b64d-95841ca0b247
searchType: searchSource
timeWindowSize: 5
timeWindowUnit: m
threshold:
- 1000
thresholdComparator: ">"
size: 100
aggType: count
groupBy: all
excludeHitsFromPreviousRun: true
created_by: elastic
updated_by: elastic
created_at: '2023-07-14T20:24:50.729Z'
updated_at: '2023-07-14T20:24:50.729Z'
api_key_owner: elastic
api_key_created_by_user: false
throttle: null
notify_when: null
mute_all: false
muted_alert_ids: []
scheduled_task_id: 7bd506d0-2284-11ee-8fad-6101956ced88
execution_status:
status: pending
last_execution_date: '2023-07-14T20:24:50.729Z'
revision: 0
running: false

View file

@ -0,0 +1,38 @@
summary: Elasticsearch query rule (DSL)
description: >
Create an Elasticsearch query rule that uses Elasticsearch query domain specific language (DSL) to define its query and a server log connector to send notifications.
value:
actions:
- group: query matched
params:
level: info
message: "The system has detected {{alerts.new.count}} new, {{alerts.ongoing.count}} ongoing, and {{alerts.recovered.count}} recovered alerts."
id: fdbece50-406c-11ee-850e-c71febc4ca7f
frequency:
throttle: "1d"
summary: true
notify_when: onThrottleInterval
- group: recovered
params:
level: info
message: Recovered
id: fdbece50-406c-11ee-850e-c71febc4ca7f
frequency:
summary: false
notify_when: onActionGroupChange
consumer: alerts
name: my Elasticsearch query rule
params:
esQuery: '"""{"query":{"match_all" : {}}}"""'
index:
- kibana_sample_data_logs
size: 100
threshold:
- 100
thresholdComparator: ">"
timeField: "@timestamp"
timeWindowSize: 1
timeWindowUnit: d
rule_type_id: .es-query
schedule:
interval: 1d

View file

@ -0,0 +1,65 @@
summary: Elasticsearch query rule (DSL)
description: The response for successfully creating an Elasticsearch query rule that uses Elasticsearch query domain specific language (DSL).
value:
id: 58148c70-407f-11ee-850e-c71febc4ca7f
enabled: true
name: my Elasticsearch query rule
tags: []
rule_type_id: .es-query
consumer: alerts
schedule:
interval: 1d
actions:
- group: query matched
id: fdbece50-406c-11ee-850e-c71febc4ca7f
params:
level: info
message: "The system has detected {{alerts.new.count}} new, {{alerts.ongoing.count}} ongoing, and {{alerts.recovered.count}} recovered alerts."
connector_type_id: .server-log
frequency:
summary: true
notify_when: onThrottleInterval
throttle: "1d"
uuid: 53f3c2a3-e5d0-4cfa-af3b-6f0881385e78
- group: recovered
id: fdbece50-406c-11ee-850e-c71febc4ca7f
params:
level: info
message: Recovered
connector_type_id: .server-log
frequency:
summary: false
notify_when: onActionGroupChange
throttle: null
uuid: 2324e45b-c0df-45c7-9d70-4993e30be758
params:
thresholdComparator: ">"
timeWindowSize: 1
timeWindowUnit: d
threshold:
- 100
size: 100
timeField: "@timestamp"
index:
- kibana_sample_data_logs
esQuery: '"""{"query":{"match_all" : {}}}"""'
excludeHitsFromPreviousRun: true
aggType: count
groupBy: all
searchType: esQuery
scheduled_task_id: 58148c70-407f-11ee-850e-c71febc4ca7f
created_by: elastic
updated_by: elastic
created_at: '2023-08-22T00:03:38.263Z'
updated_at: '2023-08-22T00:03:38.263Z'
api_key_owner: elastic
api_key_created_by_user: false
throttle: null
mute_all: false
notify_when: null
muted_alert_ids: []
execution_status:
status: pending
last_execution_date: '2023-08-22T00:03:38.263Z'
revision: 0
running: false

View file

@ -0,0 +1,29 @@
requestBody:
content:
application/json:
examples:
createEsQueryEsqlRuleRequest:
$ref: "./es_query/create_es_query_esql_rule_request.yaml"
createEsQueryRuleRequest:
$ref: "./es_query/create_es_query_rule_request.yaml"
createEsQueryKqlRuleRequest:
$ref: "./es_query/create_es_query_kql_rule_request.yaml"
createIndexThresholdRuleRequest:
$ref: "./index_threshold/create_index_threshold_rule_request.yaml"
createTrackingContainmentRuleRequest:
$ref: "./geo_containment/create_tracking_containment_rule_request.yaml"
responses:
200:
content:
application/json:
examples:
createEsQueryEsqlRuleResponse:
$ref: "./es_query/create_es_query_esql_rule_response.yaml"
createEsQueryRuleResponse:
$ref: "./es_query/create_es_query_rule_response.yaml"
createEsQueryKqlRuleResponse:
$ref: "./es_query/create_es_query_kql_rule_response.yaml"
createIndexThresholdRuleResponse:
$ref: "./index_threshold/create_index_threshold_rule_response.yaml"
createTrackingContainmentRuleResponse:
$ref: "./geo_containment/create_tracking_containment_rule_response.yaml"

View file

@ -0,0 +1,20 @@
summary: Tracking containment rule
description: >
Create a tracking containment rule that checks when an entity is contained or no longer contained within a boundary.
value:
consumer: alerts
name: my tracking rule
params:
index: kibana_sample_data_logs
dateField": '@timestamp'
geoField: geo.coordinates
entity: agent.keyword
boundaryType: entireIndex
boundaryIndexTitle: boundary*
boundaryGeoField: location
boundaryNameField: name
indexId: 90943e30-9a47-11e8-b64d-95841ca0b247
boundaryIndexId: 0cd90abf-abe7-44c7-909a-f621bbbcfefc
rule_type_id: .geo-containment
schedule:
interval: 1h

View file

@ -0,0 +1,51 @@
summary: Tracking containment rule
description: The response for successfully creating a tracking containment rule.
value:
id: b6883f9d-5f70-4758-a66e-369d7c26012f
name: my tracking rule
tags: []
enabled: true
consumer: alerts
throttle: null
revision: 1
running: false
schedule:
interval: 1h
params:
index: kibana_sample_data_logs
dateField: '@timestamp'
geoField: geo.coordinates
entity: agent.keyword
boundaryType: entireIndex
boundaryIndexTitle: boundary*
boundaryGeoField: location
boundaryNameField: name
indexId: 90943e30-9a47-11e8-b64d-95841ca0b247
boundaryIndexId: 0cd90abf-abe7-44c7-909a-f621bbbcfefc
rule_type_id: .geo-containment
created_by: elastic
updated_by: elastic
created_at: '2024-02-14T19:52:55.920Z'
updated_at: '2024-02-15T03:24:32.574Z'
api_key_owner: elastic
notify_when: null
mute_all: false
muted_alert_ids: []
scheduled_task_id: b6883f9d-5f70-4758-a66e-369d7c26012f
execution_status:
status: ok
last_execution_date: '2024-02-15T03:25:38.125Z'
last_duration: 74
actions: []
last_run:
alerts_count:
active: 0
new: 0
recovered: 0
ignored: 0
outcome_msg: null
outcome_order: 0
outcome: succeeded
warning: null
next_run: '2024-02-15T03:26:38.033Z'
api_key_created_by_user: false

View file

@ -12,6 +12,7 @@ export { ruleParamsSchema, ruleParamsSchemaWithDefaultValue } from './latest';
export {
ruleParamsSchema as ruleParamsSchemaV1,
ruleParamsSchemaWithDefaultValue as ruleParamsSchemaWithDefaultValueV1,
createRuleParamsExamples as createRuleParamsExamplesV1,
} from './v1';
export type { RuleParams } from './latest';

View file

@ -0,0 +1,36 @@
summary: Index threshold rule
description: >
Create an index threshold rule that uses a server log connector to send notifications when the threshold is met.
value:
actions:
- id: 48de3460-f401-11ed-9f8e-399c75a2deeb
frequency:
notify_when: onActionGroupChange
summary: false
group: threshold met
params:
level: info
message: "Rule '{{rule.name}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
alert_delay:
active: 3
consumer: alerts
name: my rule
params:
aggType: avg
termSize: 6
thresholdComparator: ">"
timeWindowSize: 5
timeWindowUnit: m
groupBy: top
threshold:
- 1000
index:
- .test-index
timeField: "@timestamp"
aggField: sheet.version
termField: name.keyword
rule_type_id: .index-threshold
schedule:
interval: 1m
tags:
- cpu

View file

@ -0,0 +1,56 @@
summary: Index threshold rule
description: The response for successfully creating an index threshold rule.
value:
actions:
- group: threshold met
id: dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2
uuid: 07aef2a0-9eed-4ef9-94ec-39ba58eb609d
connector_type_id: .server-log
frequency:
notify_when: onActionGroupChange
summary: false
throttle: null
params:
level: info
message: "Rule {{rule.name}} is active for group {{context.group} :\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}\n- Timestamp: {{context.date}}"
alert_delay:
active: 3
api_key_created_by_user: false
api_key_owner: elastic
consumer: alerts
created_at: '2022-06-08T17:20:31.632Z'
created_by: elastic
enabled: true
execution_status:
last_execution_date: '2022-06-08T17:20:31.632Z'
status: pending
id: 41893910-6bca-11eb-9e0d-85d233e3ee35
muted_alert_ids: []
mute_all: false
name: my rule
notify_when: null
params:
aggType: avg
termSize: 6
thresholdComparator: ">"
timeWindowSize: 5
timeWindowUnit: m
groupBy: top
threshold:
- 1000
index:
- ".test-index"
timeField: "@timestamp"
aggField: sheet.version
termField: name.keyword
revision: 0
rule_type_id: .index-threshold
running: false
schedule:
interval: 1m
scheduled_task_id: 425b0800-6bca-11eb-9e0d-85d233e3ee35
tags:
- cpu
throttle: null
updated_at: '2022-06-08T17:20:31.632Z'
updated_by: elastic

View file

@ -6,7 +6,7 @@
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import path from 'node:path';
import type { TypeOf } from '@kbn/config-schema';
import { schema } from '@kbn/config-schema';
@ -23,5 +23,8 @@ export const ruleParamsSchemaWithDefaultValue = schema.recordOf(
}
);
export const createRuleParamsExamples = () =>
path.join(__dirname, 'examples_create_rule_params.yaml');
export type RuleParams = TypeOf<typeof ruleParamsSchema>;
export type RuleParamsWithDefaultValue = TypeOf<typeof ruleParamsSchemaWithDefaultValue>;

View file

@ -27,6 +27,7 @@ export {
actionSchema as actionSchemaV1,
createParamsSchema as createParamsSchemaV1,
createBodySchema as createBodySchemaV1,
createRuleParamsExamplesV1,
} from './schemas/v1';
export type {

View file

@ -6,7 +6,10 @@
*/
import { schema } from '@kbn/config-schema';
import { ruleParamsSchemaWithDefaultValueV1 } from '@kbn/response-ops-rule-params';
import {
ruleParamsSchemaWithDefaultValueV1,
createRuleParamsExamplesV1,
} from '@kbn/response-ops-rule-params';
import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation';
import { notifyWhenSchemaV1, alertDelaySchemaV1 } from '../../../response';
import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query';
@ -188,6 +191,8 @@ export const createBodySchema = schema.object({
flapping: schema.maybe(schema.nullable(flappingSchemaV1)),
});
export { createRuleParamsExamplesV1 };
export const createParamsSchema = schema.object({
id: schema.maybe(
schema.string({

View file

@ -11,6 +11,7 @@ export type { FindRulesRequestQuery, FindRulesResponse } from './types/latest';
export {
findRulesRequestQuerySchema as findRulesRequestQuerySchemaV1,
findRulesInternalRequestBodySchema as findRulesInternalRequestBodySchemaV1,
findRuleParamsExamples as findRuleParamsExamplesV1,
} from './schemas/v1';
export type {

View file

@ -0,0 +1,9 @@
responses:
200:
content:
application/json:
examples:
findRulesResponse:
$ref: './examples_find_rule_response_conditional_action.yaml'
findConditionalActionRulesResponse:
$ref: './examples_find_rules_response.yaml'

View file

@ -4,9 +4,11 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import path from 'node:path';
import { schema } from '@kbn/config-schema';
export const findRuleParamsExamples = () => path.join(__dirname, 'examples_find_rules.yaml');
export const findRulesRequestQuerySchema = schema.object({
per_page: schema.number({
defaultValue: 10,

View file

@ -26,6 +26,7 @@ export {
actionAlertsFilterSchema as actionAlertsFilterSchemaV1,
actionSchema as actionSchemaV1,
updateBodySchema as updateBodySchemaV1,
updateRuleParamsExamples as updateRuleParamsExamplesV1,
updateParamsSchema as updateParamsSchemaV1,
} from './schemas/v1';

View file

@ -0,0 +1,13 @@
requestBody:
content:
application/json:
examples:
updateRuleRequest:
$ref: './examples_update_rule.request.yaml'
responses:
200:
content:
application/json:
examples:
updateRuleResponse:
$ref: './examples_update_rule.response.yaml'

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import path from 'node:path';
import { schema } from '@kbn/config-schema';
import { ruleParamsSchemaWithDefaultValueV1 } from '@kbn/response-ops-rule-params';
import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation';
@ -12,6 +13,8 @@ import { notifyWhenSchemaV1, alertDelaySchemaV1 } from '../../../response';
import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query';
import { flappingSchemaV1 } from '../../../common';
export const updateRuleParamsExamples = () => path.join(__dirname, 'examples_update_rule.yaml');
export const actionFrequencySchema = schema.object({
summary: schema.boolean({
meta: { description: 'Indicates whether the action is a summary.' },
@ -121,6 +124,8 @@ export const actionSchema = schema.object(
}
);
export const findRuleParamsExamples = () => path.join(__dirname, 'examples_update_rule.yaml');
export const updateBodySchema = schema.object({
name: schema.string({
meta: {

View file

@ -14,6 +14,7 @@ import type {
import {
createBodySchemaV1,
createParamsSchemaV1,
createRuleParamsExamplesV1,
} from '../../../../../common/routes/rule/apis/create';
import type { RuleParamsV1 } from '../../../../../common/routes/rule/response';
import { ruleResponseSchemaV1 } from '../../../../../common/routes/rule/response';
@ -39,6 +40,7 @@ export const createRuleRoute = ({ router, licenseState, usageCounter }: RouteOpt
access: 'public',
summary: `Create a rule`,
tags: ['oas-tag:alerting'],
oasOperationObject: createRuleParamsExamplesV1,
},
validate: {
request: {

View file

@ -11,7 +11,10 @@ import type {
FindRulesRequestQueryV1,
FindRulesResponseV1,
} from '../../../../../common/routes/rule/apis/find';
import { findRulesRequestQuerySchemaV1 } from '../../../../../common/routes/rule/apis/find';
import {
findRulesRequestQuerySchemaV1,
findRuleParamsExamplesV1,
} from '../../../../../common/routes/rule/apis/find';
import type { RuleParamsV1 } from '../../../../../common/routes/rule/response';
import { ruleResponseSchemaV1 } from '../../../../../common/routes/rule/response';
import type { ILicenseState } from '../../../../lib';
@ -35,6 +38,7 @@ export const findRulesRoute = (
access: 'public',
summary: 'Get information about rules',
tags: ['oas-tag:alerting'],
oasOperationObject: findRuleParamsExamplesV1,
},
validate: {
request: {

View file

@ -14,6 +14,7 @@ import type {
import {
updateBodySchemaV1,
updateParamsSchemaV1,
updateRuleParamsExamplesV1,
} from '../../../../../common/routes/rule/apis/update';
import type { RuleParamsV1 } from '../../../../../common/routes/rule/response';
import { ruleResponseSchemaV1 } from '../../../../../common/routes/rule/response';
@ -40,6 +41,7 @@ export const updateRuleRoute = (
access: 'public',
summary: `Update a rule`,
tags: ['oas-tag:alerting'],
oasOperationObject: updateRuleParamsExamplesV1,
},
validate: {
request: {

View file

@ -71,7 +71,7 @@
"@jridgewell/gen-mapping" "^0.1.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@apidevtools/json-schema-ref-parser@11.7.2", "@apidevtools/json-schema-ref-parser@^11.5.5":
"@apidevtools/json-schema-ref-parser@11.7.2":
version "11.7.2"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz#cdf3e0aded21492364a70e193b45b7cf4177f031"
integrity sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==
@ -80,6 +80,15 @@
"@types/json-schema" "^7.0.15"
js-yaml "^4.1.0"
"@apidevtools/json-schema-ref-parser@^11.5.5", "@apidevtools/json-schema-ref-parser@^11.9.1":
version "11.9.1"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.1.tgz#51d297224f6ee1dab40dfb2dfe298812b8e0ef9c"
integrity sha512-OvyhwtYaWSTfo8NfibmFlgl+pIMaBOmN0OwZ3CPaGscEK3B8FCVDuQ7zgxY8seU/1kfSvNWnyB0DtKJyNLxX7g==
dependencies:
"@jsdevtools/ono" "^7.1.3"
"@types/json-schema" "^7.0.15"
js-yaml "^4.1.0"
"@apidevtools/json-schema-ref-parser@^9.0.6":
version "9.0.9"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b"