diff --git a/.github/workflows/auto-approve-bundled-package-updates.yml b/.github/workflows/auto-approve-bundled-package-updates.yml new file mode 100644 index 00000000000000..5cf8655f37f73b --- /dev/null +++ b/.github/workflows/auto-approve-bundled-package-updates.yml @@ -0,0 +1,18 @@ +on: + pull_request: + types: + - opened + paths: + - 'fleet_packages.json' + +jobs: + approve: + name: Auto-approve bundled package updates + runs-on: ubuntu-latest + if: | + startsWith(github.event.pull_request.head.ref, 'update-bundled-packages') && + github.event.pull_request.user.login == 'elasticmachine' + permissions: + pull-requests: write + steps: + - uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 # v4.0.0 diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml deleted file mode 100644 index 903c5a6ec36f9a..00000000000000 --- a/.github/workflows/backport.yml +++ /dev/null @@ -1,32 +0,0 @@ -on: - pull_request_target: - branches: ['main'] - types: ['labeled', 'closed'] - -env: - NODE_ENV: kibana-github-action - -jobs: - backport: - name: Backport PR - runs-on: ubuntu-latest - if: | - github.event.pull_request.merged == true - && contains(github.event.pull_request.labels.*.name, 'auto-backport') - && ( - (github.event.action == 'labeled' && github.event.label.name == 'auto-backport') - || (github.event.action == 'closed') - ) - steps: - - name: Backport Action - uses: sorenlouv/backport-github-action@v8.9.7 - with: - github_token: ${{secrets.KIBANAMACHINE_TOKEN}} - - - name: Info log - if: ${{ success() }} - run: cat /home/runner/.backport/backport.info.log - - - name: Debug log - if: ${{ failure() }} - run: cat /home/runner/.backport/backport.debug.log diff --git a/.github/workflows/on-merge.yml b/.github/workflows/on-merge.yml index ae1b80bd94dc41..20596bf155910b 100644 --- a/.github/workflows/on-merge.yml +++ b/.github/workflows/on-merge.yml @@ -10,7 +10,6 @@ jobs: runs-on: ubuntu-latest if: | github.event.pull_request.merged == true - && !contains(github.event.pull_request.labels.*.name, 'auto-backport') && ( ( github.event.action == 'labeled' && ( @@ -19,6 +18,7 @@ jobs: || github.event.label.name == 'backport:current-major' || github.event.label.name == 'backport:all-open' || github.event.label.name == 'backport:version' + || github.event.label.name == 'auto-backport' ) ) || (github.event.action == 'closed') diff --git a/dev_docs/contributing/how_we_use_github.mdx b/dev_docs/contributing/how_we_use_github.mdx index 69eb3ba8351c63..4e59b59c7abebe 100644 --- a/dev_docs/contributing/how_we_use_github.mdx +++ b/dev_docs/contributing/how_we_use_github.mdx @@ -207,10 +207,11 @@ it's notable and affects our users) and which section it appears in. For example The following labels are related to backporting PRs: -- `auto-backport`: Automatically backport this PR (to the branches related to +- `backport:version`: Automatically backport this PR (to the branches related to version labels) after it's merged. Requires adding desired target versions labels. - `backport:prev-minor`: Automatically backport to one lower minor version. -- `backport:prev-major`: Automatically backport to the latest minor version of one lower major version. +- `backport:prev-major`: Automatically backport to all minor version of one lower major version. +- `backport:current-major`: Automatically backport to all minor version of the current major version. - `backport:all-open`: Automatically backport to all generally available versions. This functionally is equivalent to backport:prev-major at the time of writing. - `backport:skip`: This PR does not require backporting. - `backport`: This PR was backported (added by CI). diff --git a/dev_docs/tutorials/submit_a_pull_request.mdx b/dev_docs/tutorials/submit_a_pull_request.mdx index 0402a533b24985..99033f17ebef3f 100644 --- a/dev_docs/tutorials/submit_a_pull_request.mdx +++ b/dev_docs/tutorials/submit_a_pull_request.mdx @@ -55,7 +55,7 @@ If this is a new branch, you will see a link in your terminal that points you di 2. Features: Add labels for any relevant feature areas, e.g. `Feature:Development` 3. Team: Most PRs should have at least one `Team:` label. Add labels for teams that should follow or are responsible for the pull request. 4. Release Note: Add `release_note:skip` if this pull request should not automatically get added to release notes for Kibana - 5. Auto Backport: Add `auto-backport` if you'd like your pull request automatically backported to all labeled versions. + 5. Auto Backport: Add `backport:version` if you'd like your pull request automatically backported to all labeled versions. 6. Submit the pull request. If it's not quite ready for review, it can also be submitted as a Draft pull request. ![Screenshot of Compare and pull request header](../assets/pr_header.png) diff --git a/docs/action-type-template.asciidoc b/docs/action-type-template.asciidoc index 9bd61039712b3a..b9d7ca3c6326cf 100644 --- a/docs/action-type-template.asciidoc +++ b/docs/action-type-template.asciidoc @@ -36,8 +36,7 @@ Add preconfigured settings for this connector type in alert-action-settings.asci [[-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. +You can test connectors as you're creating or editing the connector in {kib}. actions have the following properties. diff --git a/docs/api/actions-and-connectors.asciidoc b/docs/api/actions-and-connectors.asciidoc index 6cf1a307cecb92..2ab848e900d461 100644 --- a/docs/api/actions-and-connectors.asciidoc +++ b/docs/api/actions-and-connectors.asciidoc @@ -1,23 +1,7 @@ [[actions-and-connectors-api]] == Action and connector APIs -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - For information about the actions and connectors that {kib} supports, refer to <>. -include::actions-and-connectors/create.asciidoc[leveloffset=+1] -include::actions-and-connectors/delete.asciidoc[leveloffset=+1] -include::actions-and-connectors/get.asciidoc[leveloffset=+1] -include::actions-and-connectors/get_all.asciidoc[leveloffset=+1] -include::actions-and-connectors/list.asciidoc[leveloffset=+1] -include::actions-and-connectors/execute.asciidoc[leveloffset=+1] -include::actions-and-connectors/update.asciidoc[leveloffset=+1] -include::actions-and-connectors/legacy/index.asciidoc[] -include::actions-and-connectors/legacy/get.asciidoc[] -include::actions-and-connectors/legacy/get_all.asciidoc[] -include::actions-and-connectors/legacy/list.asciidoc[] -include::actions-and-connectors/legacy/create.asciidoc[] -include::actions-and-connectors/legacy/update.asciidoc[] -include::actions-and-connectors/legacy/execute.asciidoc[] -include::actions-and-connectors/legacy/delete.asciidoc[] +For the latest API details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. diff --git a/docs/api/actions-and-connectors/create.asciidoc b/docs/api/actions-and-connectors/create.asciidoc deleted file mode 100644 index a9f239ed0fdb3a..00000000000000 --- a/docs/api/actions-and-connectors/create.asciidoc +++ /dev/null @@ -1,649 +0,0 @@ -[[create-connector-api]] -== Create connector API -++++ -Create connector -++++ - -Creates a connector. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[[create-connector-api-request]] -=== {api-request-title} - -`POST :/api/actions/connector` - -`POST :/s//api/actions/connector` - -=== {api-prereq-title} - -You must have `all` privileges for the *{connectors-feature}* feature in the -*Management* section of the <>. - -[[create-connector-api-path-params]] -=== {api-path-parms-title} - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided - in the URL, the default space is used. - -[role="child_attributes"] -[[create-connector-api-request-body]] -=== {api-request-body-title} - -`config`:: -(Required^*^, object) The configuration for the connector. Configuration properties -vary depending on the connector type. For example: -+ --- -// tag::connector-config[] -.Config properties -[%collapsible%open] -==== - -.{ibm-r} connectors -[%collapsible%open] -===== -`apiUrl`:: -(Required, string) The {ibm-r} instance URL. - -`orgId`:: -(Required, string) The {ibm-r} organization ID. - -For more information, refer to <>. -===== - -.Index connectors -[%collapsible%open] -===== - -`executionTimeField`:: -(Optional, string) Specifies a field that will contain the time the alert -condition was detected. The default value is `null`. - -`index`:: -(Required, string) The {es} index to be written to. - -`refresh`:: -(Optional, boolean) The {ref}/docs-refresh.html[refresh] policy for the write -request. The default value is `false`. - -For more information, refer to <>. -===== - -.{jira} connectors -[%collapsible%open] -===== - -`apiUrl`:: -(Required, string) The {jira} instance URL. - -`projectKey`:: -(Required, string) The {jira} project key. - -For more information, refer to <>. -===== - -.Opsgenie connectors -[%collapsible%open] -===== - -`apiUrl`:: -(Required, string) The Opsgenie URL. For example, `https://api.opsgenie.com` or -`https://api.eu.opsgenie.com`. If you are using the `xpack.actions.allowedHosts` -setting, make sure the hostname is added to the allowed hosts. - -For more information, refer to <>. -===== - -.{sn-itom}, {sn-itsm}, and {sn-sir} connectors -[%collapsible%open] -===== -`apiUrl`:: -(Required, string) The {sn} instance URL. - -`clientId`:: -(Required^*^, string) The client ID assigned to your OAuth application. This -property is required when `isOAuth` is `true`. - -`isOAuth`:: -(Optional, string) The type of authentication to use. The default value is -`false`, which means basic authentication is used instead of open authorization -(OAuth). - -`jwtKeyId`:: -(Required^*^, string) The key identifier assigned to the JWT verifier map of -your OAuth application. This property is required when `isOAuth` is `true`. - -`userIdentifierValue`:: -(Required^*^, string) The identifier to use for OAuth authentication. This -identifier should be the user field you selected when you created an OAuth -JWT API endpoint for external clients in your {sn} instance. For example, if -the selected user field is `Email`, the user identifier should be the user's -email address. This property is required when `isOAuth` is `true`. - -`usesTableApi`:: -(Optional, boolean) Determines whether the connector uses the Table API or the -Import Set API. This property is supported only for {sn-itsm} and {sn-sir} -connectors. The default value is `true`. -+ -NOTE: If this property is set to false, the Elastic application should be -installed in {sn}. -===== - -.{swimlane} connectors -[%collapsible%open] -===== -`apiUrl`:: -(Required, string) The {swimlane} instance URL. - -`appId`:: -(Required, string) The {swimlane} application ID. - -`connectorType`:: -(Required, String) The type of the connector. Valid values are: `all`, `alerts`, `cases`. - -`mappings`:: -(Optional, object) The field mapping. -+ -.Mappings properties -[%collapsible%open] -====== - -`alertIdConfig`::: -(Optional, object) Mapping for the alert ID. - -`fieldType`:::: -(Required, string) The type of the field in {swimlane}. - -`id`:::: -(Required, string) The id of the field in {swimlane}. - -`key`:::: -(Required, string) The key of the field in {swimlane}. - -`name`:::: -(Required, string) The name of the field in {swimlane}. - -`caseIdConfig`::: -(Optional, object) Mapping for the case ID. - -`fieldType`:::: -(Required, string) The type of the field in {swimlane}. - -`id`:::: -(Required, string) The id of the field in {swimlane}. - -`key`:::: -(Required, string) The key of the field in {swimlane}. - -`name`:::: -(Required, string) The name of the field in {swimlane}. - -`caseNameConfig`::: -(Optional, object) Mapping for the case name. - -`fieldType`:::: -(Required, string) The type of the field in {swimlane}. - -`id`:::: -(Required, string) The id of the field in {swimlane}. - -`key`:::: -(Required, string) The key of the field in {swimlane}. - -`name`:::: -(Required, string) The name of the field in {swimlane}. - -`commentsConfig`::: -(Optional, object) Mapping for the case comments. - -`fieldType`:::: -(Required, string) The type of the field in {swimlane}. - -`id`:::: -(Required, string) The id of the field in {swimlane}. - -`key`:::: -(Required, string) The key of the field in {swimlane}. - -`name`:::: -(Required, string) The name of the field in {swimlane}. - -`descriptionConfig`::: -(Optional, object) Mapping for the case description. - -`fieldType`:::: -(Required, string) The type of the field in {swimlane}. - -`id`:::: -(Required, string) The id of the field in {swimlane}. - -`key`:::: -(Required, string) The key of the field in {swimlane}. - -`name`:::: -(Required, string) The name of the field in {swimlane}. - -`ruleNameConfig`::: -(Optional, object) Mapping for the name of the alert's rule. - -`fieldType`:::: -(Required, string) The type of the field in {swimlane}. - -`id`:::: -(Required, string) The id of the field in {swimlane}. - -`key`:::: -(Required, string) The key of the field in {swimlane}. - -`name`:::: -(Required, string) The name of the field in {swimlane}. - -`severityConfig`::: -(Optional, object) Mapping for the severity. - -`fieldType`:::: -(Required, string) The type of the field in {swimlane}. - -`id`:::: -(Required, string) The id of the field in {swimlane}. - -`key`:::: -(Required, string) The key of the field in {swimlane}. - -`name`:::: -(Required, string) The name of the field in {swimlane}. - -====== -For more information, refer to <>. -===== - -.{webhook-cm} connectors -[%collapsible%open] -===== - -`createCommentJson`:: -(Optional, string) A JSON payload sent to the create comment URL to create a -case comment. You can use variables to add Kibana Cases data to the payload. The -required variable is `case.comment`. For example: -+ -[source,json] ----- -{ - "body": {{{case.comment}}} -} ----- -+ -NOTE: Due to Mustache template variables (the text enclosed in triple braces, -for example, `{{{case.title}}}`), the JSON is not validated when you create the -connector. The JSON is validated once the Mustache variables have been placed -when the REST method runs. Manually ensure that the JSON is valid, -disregarding the Mustache variables, so the later validation will pass. - -`createCommentMethod`:: -(Optional, string) The REST API HTTP request method to create a case comment in -the third-party system. Valid values are either `patch`, `post`, and `put`. The -default value is `put`. - -`createCommentUrl`:: -(Optional, string) The REST API URL to create a case comment by ID in the -third-party system. You can use a variable to add the external system ID to the -URL. If you are using the `xpack.actions.allowedHosts` setting, make sure the -hostname is added to the allowed hosts. For example: -+ -[source,text] ----- -https://testing-jira.atlassian.net/rest/api/2/issue/{{{external.system.id}}}/comment ----- - -`createIncidentJson`:: -(Required, string) A JSON payload sent to the create case URL to create a case. You -can use variables to add case data to the payload. Required variables are -`case.title` and `case.description`. For example: -+ -[source,json] ----- -{ - "fields": { - "summary": {{{case.title}}}, - "description": {{{case.description}}}, - "labels": {{{case.tags}}} - } -} ----- -+ -NOTE: Due to Mustache template variables (which is the text enclosed in triple -braces, for example, `{{{case.title}}}`), the JSON is not validated when you -create the connector. The JSON is validated after the Mustache variables have -been placed when REST method runs. Manually ensure that the JSON is valid to -avoid future validation errors; disregard Mustache variables during your review. - -`createIncidentMethod`:: -(Optional, string) The REST API HTTP request method to create a case in the -third-party system. Valid values are `patch`, `post`, and `put`. The default -value is `post`. - -`createIncidentResponseKey`:: -(Required, string) The JSON key in the create case response that contains the -external case ID. - -`createIncidentUrl`:: -(Required, string) The REST API URL to create a case in the third-party system. -If you are using the `xpack.actions.allowedHosts` setting, make sure the -hostname is added to the allowed hosts. - -`getIncidentResponseExternalTitleKey`:: -(Required, string) The JSON key in get case response that contains the external -case title. - -`getIncidentUrl`:: -(Required, string) The REST API URL to get the case by ID from the third-party -system. If you are using the `xpack.actions.allowedHosts` setting, make sure the -hostname is added to the allowed hosts. You can use a variable to add the -external system ID to the URL. For example: -+ -[source,text] ----- -https://testing-jira.atlassian.net/rest/api/2/issue/{{{external.system.id}}} ----- -+ -NOTE: Due to Mustache template variables (the text enclosed in triple braces, -for example, `{{{case.title}}}`), the JSON is not validated when you create the -connector. The JSON is validated after the Mustache variables have been placed -when REST method runs. Manually ensure that the JSON is valid, disregarding the -Mustache variables, so the later validation will pass. - -`hasAuth`:: -(Optional, boolean) If true, a username and password for login type authentication -must be provided. The default value is `true`. - -`headers`:: -(Optional, string) A set of key-value pairs sent as headers with the request -URLs for the create case, update case, get case, and create comment methods. - -`updateIncidentJson`:: -(Required, string) The JSON payload sent to the update case URL to update the -case. You can use variables to add Kibana Cases data to the payload. Required -variables are `case.title` and `case.description`. For example: -+ -[source,json] ----- -{ - "fields": { - "summary": {{{case.title}}}, - "description": {{{case.description}}}, - "labels": {{{case.tags}}} - } -} ----- -+ -NOTE: Due to Mustache template variables (which is the text enclosed in triple -braces, for example, `{{{case.title}}}`), the JSON is not validated when you -create the connector. The JSON is validated after the Mustache variables have -been placed when REST method runs. Manually ensure that the JSON is valid to -avoid future validation errors; disregard Mustache variables during your review. - -`updateIncidentMethod`:: -(Optional, string) The REST API HTTP request method to update the case in the -third-party system. Valid values are `patch`, `post`, and `put`. The default -value is `put`. - -`updateIncidentUrl`:: -(Required, string) The REST API URL to update the case by ID in the third-party -system. You can use a variable to add the external system ID to the URL. If you -are using the `xpack.actions.allowedHosts` setting, make sure the hostname is -added to the allowed hosts. For example: -+ -[source,text] ----- -https://testing-jira.atlassian.net/rest/api/2/issue/{{{external.system.ID}}} ----- - -`viewIncidentUrl`:: -(Required, string) The URL to view the case in the external system. You can use -variables to add the external system ID or external system title to the URL.For example: -+ -[source,text] ----- -https://testing-jira.atlassian.net/browse/{{{external.system.title}}} ----- - -For more information, refer to <>. -===== - -This object is not required for server log connectors. - -For more configuration properties, refer to <>. -==== -// end::connector-config[] --- - -`connector_type_id`:: -(Required, string) The connector type ID for the connector. For example, -`.cases-webhook`, `.index`, `.jira`, `.opsgenie`, `.server-log`, or `.servicenow-itom`. - -`name`:: -(Required, string) The display name for the connector. - -`secrets`:: -(Required^*^, object) The secrets configuration for the connector. Secrets -configuration properties vary depending on the connector type. For information -about the secrets configuration properties, refer to <>. -+ --- -WARNING: Remember these values. You must provide them each time you call the <> API. - -// tag::connector-secrets[] -.Secrets properties -[%collapsible%open] -==== - -.{ibm-r} connectors -[%collapsible%open] -===== -`apiKeyId`:: -(Required, string) The authentication key ID for HTTP Basic authentication. - -`apiKeySecret`:: -(Required, string) The authentication key secret for HTTP Basic authentication. -===== - -.{jira} connectors -[%collapsible%open] -===== -`apiToken`:: -(Required, string) The {jira} API authentication token for HTTP basic -authentication. - -`email`:: -(Required, string) The account email for HTTP Basic authentication. -===== - -.Opsgenie connectors -[%collapsible%open] -===== -`apiKey`:: -(Required, string) The Opsgenie API authentication key for HTTP Basic -authentication. -===== - -.{sn-itom}, {sn-itsm}, and {sn-sir} connectors -[%collapsible%open] -===== -`clientSecret`:: -(Required^*^, string) The client secret assigned to your OAuth application. This -property is required when `isOAuth` is `true`. - -`password`:: -(Required^*^, string) The password for HTTP basic authentication. This property -is required when `isOAuth` is `false`. - -`privateKey`:: -(Required^*^, string) The RSA private key that you created for use in {sn}. This -property is required when `isOAuth` is `true`. - -privateKeyPassword:: -(Required^*^, string) The password for the RSA private key. This property is -required when `isOAuth` is `true` and you set a password on your private key. - -`username`:: -(Required^*^, string) The username for HTTP basic authentication. This property -is required when `isOAuth` is `false`. - -===== - -.{swimlane} connectors -[%collapsible%open] -===== -`apiToken`:: -(string) {swimlane} API authentication token. -===== - -.{webhook-cm} connectors -[%collapsible%open] -===== -`password`:: -(Optional, string) The password for HTTP basic authentication. - -`user`:: -(Optional, string) The username for HTTP basic authentication. -===== -This object is not required for index or server log connectors. -==== -// end::connector-secrets[] --- - -[[create-connector-api-request-codes]] -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -[[create-connector-api-example]] -=== {api-examples-title} - -Create an index connector: - -[source,sh] --------------------------------------------------- -POST api/actions/connector -{ - "name": "my-connector", - "connector_type_id": ".index", - "config": { - "index": "test-index" - } -} --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", - "connector_type_id": ".index", - "name": "my-connector", - "config": { - "index": "test-index", - "refresh": false, - "executionTimeField": null - }, - "is_preconfigured": false, - "is_deprecated": false, - "is_missing_secrets": false -} --------------------------------------------------- - -Create a {jira} connector: - -[source,sh] --------------------------------------------------- -POST api/actions/connector -{ - "name": "my-jira-connector", - "connector_type_id": ".jira", - "config": { - "apiUrl": "https://elastic.atlassian.net", - "projectKey": "ES" - }, - "secrets": { - "email": "myEmail", - "apiToken": "myToken" - } -} --------------------------------------------------- -// KIBANA - -Create an {ibm-r} connector: - -[source,sh] --------------------------------------------------- -POST api/actions/connector -{ - "name": "my-resilient-connector", - "connector_type_id": ".resilient", - "config": { - "apiUrl": "https://elastic.resilient.net", - "orgId": "201" - }, - "secrets": { - "apiKeyId": "myKey", - "apiKeySecret": "myToken" - } -} --------------------------------------------------- -// KIBANA - -Create an {sn-itom} connector that uses open authorization: - -[source,sh] --------------------------------------------------- -POST api/actions/connector -{ - "name": "my-itom-connector", - "connector_type_id": ".servicenow-itom", - "config": { - "apiUrl": "https://exmaple.service-now.com/", - "clientId": "abcdefghijklmnopqrstuvwxyzabcdef", - "isOAuth": "true", - "jwtKeyId": "fedcbazyxwvutsrqponmlkjihgfedcba", - "userIdentifierValue": "testuser@email.com" - }, - "secrets": { - "clientSecret": "secretsecret", - "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nprivatekeyhere\n-----END RSA PRIVATE KEY-----" - } -} --------------------------------------------------- -// KIBANA - -Create a {swimlane} connector: - -[source,sh] --------------------------------------------------- -POST api/actions/connector -{ - "name":"my-swimlane-connector", - "connector_type_id": ".swimlane", - "config":{ - "connectorType":"all", - "mappings":{ - "ruleNameConfig":{ - "id":"b6fst", - "name":"Alert Name", - "key":"alert-name", - "fieldType":"text" - } - }, - "appId":"myAppID", - "apiUrl":"https://myswimlaneinstance.com" - }, - "secrets":{ - "apiToken":"myToken" - } -} --------------------------------------------------- -// KIBANA \ No newline at end of file diff --git a/docs/api/actions-and-connectors/delete.asciidoc b/docs/api/actions-and-connectors/delete.asciidoc deleted file mode 100644 index 7c9880951953ba..00000000000000 --- a/docs/api/actions-and-connectors/delete.asciidoc +++ /dev/null @@ -1,52 +0,0 @@ -[[delete-connector-api]] -== Delete connector API -++++ -Delete connector -++++ - -Deletes an connector by ID. - -WARNING: When you delete a connector, _it cannot be recovered_. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[discrete] -[[delete-connector-api-request]] -=== {api-request-title} - -`DELETE :/api/actions/connector/` - -`DELETE :/s//api/actions/connector/` - -[discrete] -=== {api-prereq-title} - -You must have `all` privileges for the *{connectors-feature}* feature in the -*Management* section of the -<>. - -[discrete] -[[delete-connector-api-path-params]] -=== {api-path-parms-title} - -`id`:: - (Required, string) The ID of the connector. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[discrete] -[[delete-connector-api-response-codes]] -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -[discrete] -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -DELETE api/actions/connector/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad --------------------------------------------------- -// KIBANA diff --git a/docs/api/actions-and-connectors/execute.asciidoc b/docs/api/actions-and-connectors/execute.asciidoc deleted file mode 100644 index 65e5e6035e8457..00000000000000 --- a/docs/api/actions-and-connectors/execute.asciidoc +++ /dev/null @@ -1,861 +0,0 @@ -[[execute-connector-api]] -== Run connector API -++++ -Run connector -++++ - -Runs a connector by ID. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[[execute-connector-api-request]] -=== {api-request-title} - -`POST :/api/actions/connector//_execute` - -`POST :/s//api/actions/connector//_execute` - -[[execute-connector-api-prereq]] -=== {api-prereq-title} - -You must have `read` privileges for the *{connectors-feature}* feature in the -*Management* section of the -<>. - -If you use an index connector, you must also have `all`, `create`, `index`, or -`write` {ref}/security-privileges.html[indices privileges]. - -[[execute-connector-api-desc]] -=== {api-description-title} - -You can use this API to test an <> that -involves interaction with Kibana services or integrations with third-party -systems. - -[[execute-connector-api-params]] -=== {api-path-parms-title} - -`id`:: -(Required, string) The ID of the connector. - -`space_id`:: -(Optional, string) An identifier for the space. If `space_id` is not provided in -the URL, the default space is used. - -[role="child_attributes"] -[[execute-connector-api-request-body]] -=== {api-request-body-title} - -`params`:: -(Required, object) The parameters of the connector. Parameter properties vary -depending on the connector type. For information about the parameter properties, -refer to <>. -+ --- -.`Params` properties -[%collapsible%open] -==== - -.Email connectors -[%collapsible%open] -===== -`bcc`:: -(Optional, array of strings) A list of "blind carbon copy" email addresses. -Addresses can be specified in `user@host-name` format or in name `` format. - -`cc`:: -(Optional, array of strings) A list of "carbon copy" email addresses. -Addresses can be specified in `user@host-name` format or in name `` format. - -`message`:: -(Required, string) The email message text. Markdown format is supported. - -`subject`:: -(Required, string) The subject line of the email. - -`to`:: -(Required^*^, array of strings) -A list of email addresses. -Addresses can be specified in `user@host-name` format or in name `` format. -There must be at least one recipient in `to`, `cc`, or `bcc`. - -For more information, refer to <>. -===== - -.Index connectors -[%collapsible%open] -===== -`documents`:: -(Required, array of objects) The documents to index in JSON format. - -For more information, refer to <>. -===== - -.Jira connectors -[%collapsible%open] -===== -`subAction`:: -(Required, string) The action to test. Valid values include: `fieldsByIssueType`, -`getFields`, `getIncident`, `issue`, `issues`, `issueTypes`, and `pushToService`. - -`subActionParams`:: -(Required^*^, object) The set of configuration properties, which vary depending -on the `subAction` value. This object is not required when `subAction` is -`getFields` or `issueTypes`. -+ -.Properties when `subAction` is `fieldsByIssueType` -[%collapsible%open] -====== -`id`::: -(Required, string) The Jira issue type identifier. For example, `10024`. -====== -+ -.Properties when `subAction` is `getIncident` -[%collapsible%open] -====== -`externalId`::: -(Required, string) The Jira issue identifier. For example, `71778`. -====== -+ -.Properties when `subAction` is `issue` -[%collapsible%open] -====== -`id`::: -(Required, string) The Jira issue identifier. For example, `71778`. -====== -+ -.Properties when `subAction` is `issues` -[%collapsible%open] -====== -`title`::: -(Required, string) The title of the Jira issue. -====== -+ -.Properties when `subAction` is `pushToService` -[%collapsible%open] -====== -`comments`::: -(Optional, array of objects) Additional information that is sent to Jira. -+ -.Properties of `comments` -[%collapsible%open] -======= -`comment`:::: -(string) A comment related to the incident. For example, describe how to -troubleshoot the issue. - -`commentId`:::: -(integer) A unique identifier for the comment. -======= - -`incident`::: -(Required, object) Information necessary to create or update a Jira incident. -+ -.Properties of `incident` -[%collapsible%open] -======= -`description`:::: -(Optional, string) The details about the incident. - -`externalId`:::: -(Optional, string) The Jira issue identifier. If present, the incident is -updated. Otherwise, a new incident is created. - -`labels`:::: -(Optional, array of strings) The labels for the incident. For example, -`["LABEL1"]`. NOTE: Labels cannot contain spaces. - -`issueType`:::: -(Optional, integer) The type of incident. For example, `10006`. To obtain the -list of valid values, set `subAction` to `issueTypes`. - -`parent`:::: -(Optional, string) The ID or key of the parent issue. Applies only to `Sub-task` -types of issues. - -`priority`:::: -(Optional, string) The incident priority level. For example, `Lowest`. - -`summary`:::: -(Required, string) A summary of the incident. - -`title`:::: -(Optional, string) A title for the incident, used for searching the contents of -the knowledge base. -======= -====== - -For more information, refer to <>. -===== - -.{opsgenie} connectors -[%collapsible%open] -===== -`subAction`:: -(Required, string) The action to test. Valid values include: `createAlert` and -`closeAlert`. - -`subActionParams`:: -(Required, object) The set of configuration properties, which vary depending on -the `subAction` value. -+ -.Properties when `subAction` is `createAlert` -[%collapsible%open] -====== -`actions`:::: -(Optional, array of strings) The custom actions available to the alert. - -`alias`:::: -(Optional, string) The unique identifier used for alert deduplication in {opsgenie}. - -`description`:::: -(Optional, string) A description that provides detailed information about the alert. - -`details`:::: -(Optional, object) The custom properties of the alert. For example: -`{"key1":"value1","key2":"value2"}`. - -`entity`:::: -(Optional, string) The domain of the alert. For example, the application or server -name. - -`message`:::: -(Required, string) The alert message. - -`note`:::: -(Optional, string) Additional information for the alert. - -`priority`:::: -(Optional, string) The priority level for the alert. Valid values are: `P1`, -`P2`, `P3`, `P4`, and `P5`. - -`responders`:::: -(Optional, array of objects) The entities to receive notifications about the -alert. If `type` is `user`, either `id` or `username` is required. If `type` is -`team`, either `id` or `name` is required. -+ -.Properties of `responders` objects -[%collapsible%open] -======= -`id`:::: -(Required^*^, string) The identifier for the entity. - -`name`:::: -(Required^*^, string) The name of the entity. - -`type`:::: -(Required, string) Valid values are `escalation`, `schedule`, `team`, and `user`. - -`username`:::: -(Required^*^, string) A valid email address for the user. -======= - -`source`:::: -(Optional, string) The display name for the source of the alert. - -`tags`:::: -(Optional, array of strings) The tags for the alert. - -`user`:::: -(Optional, string) The display name for the owner. - -`visibleTo`:::: -(Optional, array of objects) The teams and users that the alert will be visible -to without sending a notification. Only one of `id`, `name`, or `username` is -required. -+ -.Properties of `visibleTo` objects -[%collapsible%open] -======= -`id`:::: -(Required^*^, string) The identifier for the entity. - -`name`:::: -(Required^*^, string) The name of the entity. - -`type`:::: -(Required, string) Valid values are `team` and `user`. - -`username`:::: -(Required^*^, string) The user name. This property is required only when the -`type` is `user`. -======= -====== -+ -.Properties when `subAction` is `closeAlert` -[%collapsible%open] -====== -`alias`:::: -(Required, string) The unique identifier used for alert deduplication in {opsgenie}. -The alias must match the value used when creating the alert. - -`note`:::: -(Optional, string) Additional information for the alert. - -`source`:::: -(Optional, string) The display name for the source of the alert. - -`user`:::: -(Optional, string) The display name for the owner. -====== - -For more information, refer to <>. -===== - - -.{sn-itom} connectors -[%collapsible%open] -===== -`subAction`:: -(Required, string) The action to test. Valid values include: `addEvent` and -`getChoices`. - -`subActionParams`:: -(Required^*^, object) The set of configuration properties, which vary depending -on the `subAction` value. -+ -.Properties when `subAction` is `addEvent` -[%collapsible%open] -====== -`additional_info`:::: -(Optional, string) Additional information about the event. - -`description`:::: -(Optional, string) The details about the event. - -`event_class`:::: -(Optional, string) A specific instance of the source. - -`message_key`:::: -(Optional, string) All actions sharing this key are associated with the same -{sn} alert. The default value is `:`. - -`metric_name`:::: -(Optional, string) The name of the metric. - -`node`:::: -(Optional, string) The host that the event was triggered for. - -`resource`:::: -(Optional, string) The name of the resource. - -`severity`:::: -(Optional, string) The severity of the event. - -`source`:::: -(Optional, string) The name of the event source type. - -`time_of_event`:::: -(Optional, string) The time of the event. - -`type`:::: -(Optional, string) The type of event. -====== -+ -.Properties when `subAction` is `getChoices` -[%collapsible%open] -====== -`fields`:::: -(Required, array of strings) An array of fields. For example, `["severity"]`. -====== -===== - -.{sn-itsm} connectors -[%collapsible%open] -===== -`subAction`:: -(Required, string) The action to test. Valid values include: `getFields`, -`getIncident`, `getChoices`, and `pushToService`. - -`subActionParams`:: -(Required^*^, object) The set of configuration properties, which vary depending -on the `subAction` value. This object is not required when `subAction` is -`getFields`. -+ -.Properties when `subAction` is `getChoices` -[%collapsible%open] -====== -`fields`:::: -(Required, array of strings) An array of fields. For example, `["category","impact"]`. -====== -+ -.Properties when `subAction` is `getIncident` -[%collapsible%open] -====== -`externalId`:::: -(Required, string) The {sn-itsm} issue identifier. -====== -+ -.Properties when `subAction` is `pushToService` -[%collapsible%open] -====== -`comments`::: -(Optional, array of objects) Additional information that is sent to {sn-itsm}. -+ -.Properties of `comments` -[%collapsible%open] -======= -`comment`:::: -(string) A comment related to the incident. For example, describe how to -troubleshoot the issue. - -`commentId`:::: -(integer) A unique identifier for the comment. - -//// -version:::: -(string) TBD -//// -======= - -`incident`::: -(Required, object) Information necessary to create or update a {sn-itsm} incident. -+ -.Properties of `incident` -[%collapsible%open] -======= -`category`:::: -(Optional, string) The category of the incident. - -`correlation_display`:::: -(Optional, string) A descriptive label of the alert for correlation purposes in -{sn}. - -`correlation_id`:::: -(Optional, string) The correlation identifier for the security incident. -Connectors using the same correlation ID are associated with the same {sn} -incident. This value determines whether a new {sn} incident is created or an -existing one is updated. Modifying this value is optional; if not modified, the -rule ID and alert ID are combined as `{{ruleID}}:{{alert ID}}` to form the -correlation ID value in {sn}. The maximum character length for this value is 100 -characters. -+ -NOTE: Using the default configuration of `{{ruleID}}:{{alert ID}}` ensures -that {sn} creates a separate incident record for every generated alert that uses -a unique alert ID. If the rule generates multiple alerts that use the same alert -IDs, {sn} creates and continually updates a single incident record for the alert. - -`description`:::: -(Optional, string) The details about the incident. - -`externalId`:::: -(Optional, string) The {sn-itsm} issue identifier. If present, the incident is -updated. Otherwise, a new incident is created. - -`impact`:::: -(Optional, string) The impact in {sn-itsm}. - -`severity`:::: -(Optional, string) The severity of the incident. - -`short_description`:::: -(Required, string) A short description for the incident, used for searching the -contents of the knowledge base. - -`subcategory`:::: -(Optional, string) The subcategory in {sn-itsm}. - -`urgency`:::: -(Optional, string) The urgency in {sn-itsm}. -======= -====== -===== - -.{sn-sir} connectors -[%collapsible%open] -===== -`subAction`:: -(Required, string) The action to test. Valid values include: `getFields`, -`getIncident`, `getChoices`, and `pushToService`. - -`subActionParams`:: -(Required^*^, object) The set of configuration properties, which vary depending -on the `subAction` value. This object is not required when `subAction` is -`getFields`. -+ -.Properties when `subAction` is `getChoices` -[%collapsible%open] -====== -`fields`:::: -(Required, array of strings) An array of fields. For example, `["priority","category"]`. -====== -+ -.Properties when `subAction` is `getIncident` -[%collapsible%open] -====== -`externalId`:::: -(Required, string) The {sn-sir} issue identifier. -====== -+ -.Properties when `subAction` is `pushToService` -[%collapsible%open] -====== -`comments`::: -(Optional, array of objects) Additional information that is sent to {sn-sir}. -+ -.Properties of `comments` -[%collapsible%open] -======= -`comment`:::: -(string) A comment related to the incident. For example, describe how to -troubleshoot the issue. - -`commentId`:::: -(integer) A unique identifier for the comment. - -//// -`version`:::: -(string) TBD -//// -======= - -`incident`::: -(Required, object) Information necessary to create or update a {sn-sir} incident. -+ -.Properties of `incident` -[%collapsible%open] -======= -`category`:::: -(Optional, string) The category of the incident. - -`correlation_display`:::: -(Optional, string) A descriptive label of the alert for correlation purposes in -{sn}. - -`correlation_id`:::: -(Optional, string) The correlation identifier for the security incident. -Connectors using the same correlation ID are associated with the same {sn} -incident. This value determines whether a new {sn} incident is created or an -existing one is updated. Modifying this value is optional; if not modified, the -rule ID and alert ID are combined as `{{ruleID}}:{{alert ID}}` to form the -correlation ID value in {sn}. The maximum character length for this value is 100 -characters. -+ -NOTE: Using the default configuration of `{{ruleID}}:{{alert ID}}` ensures that -{sn} creates a separate incident record for every generated alert that uses a -unique alert ID. If the rule generates multiple alerts that use the same alert -IDs, {sn} creates and continually updates a single incident record for the alert. - -`description`:::: -(Optional, string) The details about the incident. - -`dest_ip`:::: -(Optional, string or array of strings) A list of destination IP addresses related -to the security incident. The IPs are added as observables to the security incident. - -`externalId`:::: -(Optional, string) The {sn-sir} issue identifier. If present, the incident is -updated. Otherwise, a new incident is created. - -`malware_hash`:::: -(Optional, string or array of strings) A list of malware hashes related to the -security incident. The hashes are added as observables to the security incident. - -`malware_url`:::: -(Optional, string or array of strings) A list of malware URLs related to the -security incident. The URLs are added as observables to the security incident. - -`priority`:::: -(Optional, string) The priority of the incident. - -`short_description`:::: -(Required, string) A short description for the incident, used for searching the -contents of the knowledge base. - -`source_ip`:::: -(Optional, string or array of strings) A list of source IP addresses related to -the security incident. The IPs are added as observables to the security incident. - -`subcategory`:::: -(Optional, string) The subcategory of the incident. -======= -====== -===== - -.Server log connectors -[%collapsible%open] -===== -`level`:: -(Optional, string) The log level of the message: `trace`, `debug`, `info`, -`warn`, `error`, or `fatal`. Defaults to `info`. - -`message`:: -(Required, string) The message to log. -===== - -.Slack connectors -[%collapsible%open] -===== -`message`:: -(Required^*^, string) The Slack message text, which cannot contain Markdown, images, or other advanced formatting. -It is applicable only when the connector type is `.slack`. - -`subAction`:: -(Required^*^, string) The action to test. -It is applicable only when the connector type is `.slack_api`. -Valid values include: `postMessage`, `validChannelId`. -====== - -`subActionParams`:: -(Required, object) The set of configuration properties, which vary depending -on the `subAction` value. -+ -.Properties when `subAction` is `postMessage` -[%collapsible%open] -======= -`channelIds`::: -(Optional, array of strings) The Slack channel identifier, which must be one of the allowed channels in the connector configuration. - -`channels`::: -(Optional, array of strings) -The name of a channel that your Slack app has access to. deprecated:[8.12.0] - -`text`::: -(Optional, string) The Slack message text, which cannot contain Markdown, images, or other advanced formatting. -======= -+ -.Properties when `subAction` is `validChannelId` -[%collapsible%open] -======= -`channelId`::: -(Required, string) The Slack channel identifier. For example, `C123ABC456`. -======= -====== -===== - -.{swimlane} connectors -[%collapsible%open] -===== -`subAction`:: -(Required, string) The action to test. It must be `pushToService`. - -`subActionParams`:: -(Required, object) The set of configuration properties. -+ -.Properties of `subActionParams` -[%collapsible%open] -====== -`comments`::: -(Optional, array of objects) Additional information that is sent to {swimlane}. -+ -.Properties of `comments` objects -[%collapsible%open] -======= -comment:::: -(string) A comment related to the incident. For example, describe how to -troubleshoot the issue. - -commentId:::: -(integer) A unique identifier for the comment. - -======= - -`incident`::: -(Required, object) Information necessary to create or update a {swimlane} incident. -+ -.Properties of `incident` -[%collapsible%open] -======= -`alertId`:::: -(Optional, string) The alert identifier. - -`caseId`:::: -(Optional, string) The case identifier for the incident. - -`caseName`:::: -(Optional, string) The case name for the incident. - -`description`:::: -(Optional, string) The description of the incident. - -`ruleName`:::: -(Optional, string) The rule name. - -`severity`:::: -(Optional, string) The severity of the incident. -======= -====== -===== -==== --- - -[[execute-connector-api-codes]] -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -[[execute-connector-api-example]] -=== {api-examples-title} - -Run an index connector: - -[source,sh] --------------------------------------------------- -POST api/actions/connector/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad/_execute -{ - "params": { - "documents": [ - { - "id": "test_doc_id", - "name": "test_doc_name", - "message": "hello, world" - } - ] - } -} --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "status": "ok", - "data": { - "took": 10, - "errors": false, - "items": [ - { - "index": { - "_index": "test-index", - "_id": "iKyijHcBKCsmXNFrQe3T", - "_version": 1, - "result": "created", - "_shards": { - "total": 2, - "successful": 1, - "failed": 0 - }, - "_seq_no": 0, - "_primary_term": 1, - "status": 201 - } - } - ] - }, - "connector_id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad" -} --------------------------------------------------- - -Run a server log connector: - -[source,sh] --------------------------------------------------- -POST api/actions/connector/7fc7b9a0-ecc9-11ec-8736-e7d63118c907/_execute -{ - "params": { - "level": "warn", - "message": "Test warning message" - } -} --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{"status":"ok","connector_id":"7fc7b9a0-ecc9-11ec-8736-e7d63118c907"} --------------------------------------------------- - -Retrieve the list of issue types for a Jira connector: - -[source,sh] --------------------------------------------------- -POST api/actions/connector/b3aad810-edbe-11ec-82d1-11348ecbf4a6/_execute -{ - "params": { - "subAction": "issueTypes" - } -} --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "status":"ok", - "data":[ - {"id":"10024","name":"Improvement"},{"id":"10006","name":"Task"}, - {"id":"10007","name":"Sub-task"},{"id":"10025","name":"New Feature"}, - {"id":"10023","name":"Bug"},{"id":"10000","name":"Epic"} - ], - "connector_id":"b3aad810-edbe-11ec-82d1-11348ecbf4a6" -} --------------------------------------------------- - -Create then update a {swimlane} incident: -[source,sh] --------------------------------------------------- -POST api/actions/connector/a4746470-2f94-11ed-b0e0-87533c532698/_execute -{ - "params":{ - "subAction":"pushToService", - "subActionParams":{ - "incident":{ - "description":"Description of the incident", - "caseName":"Case name", - "caseId":"1000" - }, - "comments":[ - {"commentId":"1","comment":"A comment about the incident"} - ] - } - } -} - -POST api/actions/connector/a4746470-2f94-11ed-b0e0-87533c532698/_execute -{ - "params":{ - "subAction":"pushToService", - "subActionParams":{ - "incident":{ - "caseId":"1000", - "caseName":"A new case name" - } - } - } -} --------------------------------------------------- -// KIBANA - -Retrieve the list of choices for a {sn-itom} connector: - -[source,sh] --------------------------------------------------- -POST api/actions/connector/9d9be270-2fd2-11ed-b0e0-87533c532698/_execute -{ - "params": { - "subAction": "getChoices", - "subActionParams": { - "fields": [ "severity","urgency" ] - } - } -} --------------------------------------------------- -// KIBANA - -The API returns the severity and urgency choices, for example: - -[source,sh] --------------------------------------------------- -{ - "status": "ok", - "data":[ - {"dependent_value":"","label":"Critical","value":"1","element":"severity"}, - {"dependent_value":"","label":"Major","value":"2","element":"severity"}, - {"dependent_value":"","label":"Minor","value":"3","element":"severity"}, - {"dependent_value":"","label":"Warning","value":"4","element":"severity"}, - {"dependent_value":"","label":"OK","value":"5","element":"severity"}, - {"dependent_value":"","label":"Clear","value":"0","element":"severity"}, - {"dependent_value":"","label":"1 - High","value":"1","element":"urgency"}, - {"dependent_value":"","label":"2 - Medium","value":"2","element":"urgency"}, - {"dependent_value":"","label":"3 - Low","value":"3","element":"urgency"}], - "connector_id":"9d9be270-2fd2-11ed-b0e0-87533c532698" -} --------------------------------------------------- - diff --git a/docs/api/actions-and-connectors/get.asciidoc b/docs/api/actions-and-connectors/get.asciidoc deleted file mode 100644 index 950bb37e83f571..00000000000000 --- a/docs/api/actions-and-connectors/get.asciidoc +++ /dev/null @@ -1,70 +0,0 @@ -[[get-connector-api]] -== Get connector API -++++ -Get connector -++++ - -Retrieves a connector by ID. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[discrete] -[[get-connector-api-request]] -=== {api-request-title} - -`GET :/api/actions/connector/` - -`GET :/s//api/actions/connector/` - -[discrete] -=== {api-prereq-title} - -You must have `read` privileges for the *{connectors-feature}* feature in the -*Management* section of the -<>. - -[discrete] -[[get-connector-api-params]] -=== {api-path-parms-title} - -`id`:: - (Required, string) The ID of the connector. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[discrete] -[[get-connector-api-codes]] -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -[discrete] -[[get-connector-api-example]] -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -GET api/actions/connector/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", - "connector_type_id": ".index", - "name": "my-connector", - "config": { - "index": "test-index", - "refresh": false, - "executionTimeField": null - }, - "is_preconfigured": false, - "is_deprecated": false, - "is_missing_secrets": false -} --------------------------------------------------- \ No newline at end of file diff --git a/docs/api/actions-and-connectors/get_all.asciidoc b/docs/api/actions-and-connectors/get_all.asciidoc deleted file mode 100644 index 9d4b812dc49938..00000000000000 --- a/docs/api/actions-and-connectors/get_all.asciidoc +++ /dev/null @@ -1,80 +0,0 @@ -[[get-all-connectors-api]] -== Get all connectors API -++++ -Get all connectors -++++ - -Retrieves all connectors. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[discrete] -[[get-all-connectors-api-request]] -=== {api-request-title} - -`GET :/api/actions/connectors` - -`GET :/s//api/actions/connectors` - -[discrete] -=== {api-prereq-title} - -You must have `read` privileges for the *{connectors-feature}* feature in the -*Management* section of the -<>. - -[discrete] -[[get-all-connectors-api-path-params]] -=== {api-path-parms-title} - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[discrete] -[[get-all-connectors-api-codes]] -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -[discrete] -[[get-all-connectors-api-example]] -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -GET api/actions/connectors --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -[ - { - "id": "preconfigured-mail-connector", - "connector_type_id": ".email", - "name": "email: preconfigured-mail-connector", - "is_preconfigured": true, - "is_deprecated": false, - "referenced_by_count": 0 <1> - }, - { - "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", - "connector_type_id": ".index", - "name": "my-connector", - "config": { - "index": "test-index", - "refresh": false, - "executionTimeField": null - }, - "is_preconfigured": false, - "is_deprecated": false, - "is_missing_secrets": false, - "referenced_by_count": 3 - } -] --------------------------------------------------- - -<1> `referenced_by_count` indicates the number of saved objects that reference the connector. This value is not calculated if `is_preconfigured` is `true`. \ No newline at end of file diff --git a/docs/api/actions-and-connectors/legacy/create.asciidoc b/docs/api/actions-and-connectors/legacy/create.asciidoc deleted file mode 100644 index 7653b5f8755e3c..00000000000000 --- a/docs/api/actions-and-connectors/legacy/create.asciidoc +++ /dev/null @@ -1,84 +0,0 @@ -[[actions-and-connectors-legacy-api-create]] -==== Legacy Create connector API -++++ -Legacy Create connector -++++ - -deprecated::[7.13.0,Use <> instead.] - -Creates a connector. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[[actions-and-connectors-legacy-api-create-request]] -===== Request - -`POST :/api/actions/action` - -`POST :/s//api/actions/action` - -[[actions-and-connectors-legacy-api-create-path-params]] -===== Path parameters - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[actions-and-connectors-legacy-api-create-request-body]] -===== Request body - -`name`:: - (Required, string) The display name for the connector. - -`actionTypeId`:: - (Required, string) The connector type ID for the connector. - -`config`:: - (Required, object) The configuration for the connector. Configuration properties vary depending on - the connector type. For information about the configuration properties, refer to <>. - -`secrets`:: - (Required, object) The secrets configuration for the connector. Secrets configuration properties vary - depending on the connector type. For information about the secrets configuration properties, refer to <>. -+ -WARNING: Remember these values. You must provide them each time you call the <> API. - -[[actions-and-connectors-legacy-api-create-request-codes]] -===== Response code - -`200`:: - Indicates a successful call. - -[[actions-and-connectors-legacy-api-create-example]] -===== Example - -[source,sh] --------------------------------------------------- -$ curl -X POST api/actions/action -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' -{ - "name": "my-connector", - "actionTypeId": ".index", - "config": { - "index": "test-index" - } -}' --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", - "actionTypeId": ".index", - "name": "my-connector", - "config": { - "index": "test-index", - "refresh": false, - "executionTimeField": null - }, - "isPreconfigured": false, - "isDeprecated": false, - "isMissingSecrets": false -} --------------------------------------------------- diff --git a/docs/api/actions-and-connectors/legacy/delete.asciidoc b/docs/api/actions-and-connectors/legacy/delete.asciidoc deleted file mode 100644 index d5bfa4d0eca84d..00000000000000 --- a/docs/api/actions-and-connectors/legacy/delete.asciidoc +++ /dev/null @@ -1,43 +0,0 @@ -[[actions-and-connectors-legacy-api-delete]] -==== Legacy Delete connector API -++++ -Legacy Delete connector -++++ - -deprecated::[7.13.0,Use <> instead.] - -Deletes a connector by ID. - -When you delete an connector, _it cannot be recovered_. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[[actions-and-connectors-legacy-api-delete-request]] -===== Request - -`DELETE :/api/actions/action/` - -`DELETE :/s//api/actions/action/` - -[[actions-and-connectors-legacy-api-delete-path-params]] -===== Path parameters - -`id`:: - (Required, string) The ID of the connector. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[actions-and-connectors-legacy-api-delete-response-codes]] -===== Response code - -`200`:: - Indicates a successful call. - -===== Example - -[source,sh] --------------------------------------------------- -$ curl -X DELETE api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad --------------------------------------------------- -// KIBANA diff --git a/docs/api/actions-and-connectors/legacy/execute.asciidoc b/docs/api/actions-and-connectors/legacy/execute.asciidoc deleted file mode 100644 index 0256080294be52..00000000000000 --- a/docs/api/actions-and-connectors/legacy/execute.asciidoc +++ /dev/null @@ -1,92 +0,0 @@ -[[actions-and-connectors-legacy-api-execute]] -==== Legacy Execute connector API -++++ -Legacy Execute connector -++++ - -deprecated::[7.13.0,Use <> instead.] - -Executes a connector by ID. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[[actions-and-connectors-legacy-api-execute-request]] -===== Request - -`POST :/api/actions/action//_execute` - -`POST :/s//api/actions/action//_execute` - -[[actions-and-connectors-legacy-api-execute-params]] -===== Path parameters - -`id`:: - (Required, string) The ID of the connector. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[actions-and-connectors-legacy-api-execute-request-body]] -===== Request body - -`params`:: - (Required, object) The parameters of the connector. Parameter properties vary depending on - the connector type. For information about the parameter properties, refer to <>. - -[[actions-and-connectors-legacy-api-execute-codes]] -===== Response code - -`200`:: - Indicates a successful call. - -[[actions-and-connectors-legacy-api-execute-example]] -===== Example - -[source,sh] --------------------------------------------------- -$ curl -X POST api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad/_execute -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' -{ - "params": { - "documents": [ - { - "id": "test_doc_id", - "name": "test_doc_name", - "message": "hello, world" - } - ] - } -}' --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "status": "ok", - "data": { - "took": 197, - "errors": false, - "items": [ - { - "index": { - "_index": "updated-index", - "_id": "iKyijHcBKCsmXNFrQe3T", - "_version": 1, - "result": "created", - "_shards": { - "total": 2, - "successful": 1, - "failed": 0 - }, - "_seq_no": 0, - "_primary_term": 1, - "status": 201 - } - } - ] - }, - "actionId": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad" -} --------------------------------------------------- diff --git a/docs/api/actions-and-connectors/legacy/get.asciidoc b/docs/api/actions-and-connectors/legacy/get.asciidoc deleted file mode 100644 index 62cbc6c08a6e4f..00000000000000 --- a/docs/api/actions-and-connectors/legacy/get.asciidoc +++ /dev/null @@ -1,61 +0,0 @@ -[[actions-and-connectors-legacy-api-get]] -==== Legacy Get connector API -++++ -Legacy Get connector -++++ - -deprecated::[7.13.0,Use <> instead.] - -Retrieves a connector by ID. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[[actions-and-connectors-legacy-api-get-request]] -===== Request - -`GET :/api/actions/action/` - -`GET :/s//api/actions/action/` - -[[actions-and-connectors-legacy-api-get-params]] -===== Path parameters - -`id`:: - (Required, string) The ID of the action. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[actions-and-connectors-legacy-api-get-codes]] -===== Response code - -`200`:: - Indicates a successful call. - -[[actions-and-connectors-legacy-api-get-example]] -===== Example - -[source,sh] --------------------------------------------------- -$ curl -X GET api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", - "actionTypeId": ".index", - "name": "my-connector", - "config": { - "index": "test-index", - "refresh": false, - "executionTimeField": null - }, - "isPreconfigured": false, - "isDeprecated": false, - "isMissingSecrets": false -} --------------------------------------------------- diff --git a/docs/api/actions-and-connectors/legacy/get_all.asciidoc b/docs/api/actions-and-connectors/legacy/get_all.asciidoc deleted file mode 100644 index 3b1e25100c5c4a..00000000000000 --- a/docs/api/actions-and-connectors/legacy/get_all.asciidoc +++ /dev/null @@ -1,67 +0,0 @@ -[[actions-and-connectors-legacy-api-get-all]] -==== Legacy Get all connector API -++++ -Legacy Get all connector -++++ - -deprecated::[7.13.0,Use <> instead.] - -Retrieves all connectors. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[[actions-and-connectors-legacy-api-get-all-request]] -===== Request - -`GET :/api/actions` - -`GET :/s//api/actions` - -[[actions-and-connectors-legacy-api-get-all-path-params]] -===== Path parameters - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[actions-and-connectors-legacy-api-get-all-codes]] -===== Response code - -`200`:: - Indicates a successful call. - -[[actions-and-connectors-legacy-api-get-all-example]] -===== Example - -[source,sh] --------------------------------------------------- -$ curl -X GET api/actions --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -[ - { - "id": "preconfigured-mail-action", - "actionTypeId": ".email", - "name": "email: preconfigured-mail-action", - "isPreconfigured": true, - "isDeprecated": false - }, - { - "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", - "actionTypeId": ".index", - "name": "my-action", - "config": { - "index": "test-index", - "refresh": false, - "executionTimeField": null - }, - "isPreconfigured": false, - "isDeprecated": false, - "isMissingSecrets": false - } -] --------------------------------------------------- diff --git a/docs/api/actions-and-connectors/legacy/index.asciidoc b/docs/api/actions-and-connectors/legacy/index.asciidoc deleted file mode 100644 index 591ca4df7f3fdc..00000000000000 --- a/docs/api/actions-and-connectors/legacy/index.asciidoc +++ /dev/null @@ -1,6 +0,0 @@ -[[actions-and-connectors-legacy-apis]] -=== Deprecated 7.x APIs - -These APIs are deprecated and will be removed in a future release. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. diff --git a/docs/api/actions-and-connectors/legacy/list.asciidoc b/docs/api/actions-and-connectors/legacy/list.asciidoc deleted file mode 100644 index c51fb400a442d7..00000000000000 --- a/docs/api/actions-and-connectors/legacy/list.asciidoc +++ /dev/null @@ -1,71 +0,0 @@ -[[actions-and-connectors-legacy-api-list]] -==== Legacy List connector types API -++++ -Legacy List all connector types -++++ - -deprecated::[7.13.0,Use <> instead.] - -Retrieves a list of all connector types. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[[actions-and-connectors-legacy-api-list-request]] -===== Request - -`GET :/api/actions/list_action_types` - -`GET :/s//api/actions/list_action_types` - -[[actions-and-connectors-legacy-api-list-path-params]] -===== Path parameters - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[actions-and-connectors-legacy-api-list-codes]] -===== Response code - -`200`:: - Indicates a successful call. - -[[actions-and-connectors-legacy-api-list-example]] -===== Example - -[source,sh] --------------------------------------------------- -$ curl -X GET api/actions/list_action_types --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -[ - { - "id": ".email", <1> - "name": "Email", <2> - "minimumLicenseRequired": "gold", <3> - "enabled": false, <4> - "enabledInConfig": true, <5> - "enabledInLicense": false <6> - }, - { - "id": ".index", - "name": "Index", - "minimumLicenseRequired": "basic", - "enabled": true, - "enabledInConfig": true, - "enabledInLicense": true - } -] --------------------------------------------------- - - -<1> `id` - The unique ID of the connector type. -<2> `name` - The name of the connector type. -<3> `minimumLicenseRequired` - The license required to use the connector type. -<4> `enabled` - Specifies if the connector type is enabled or disabled in {kib}. -<5> `enabledInConfig` - Specifies if the connector type is enabled or enabled in the {kib} .yml file. -<6> `enabledInLicense` - Specifies if the connector type is enabled or disabled in the license. diff --git a/docs/api/actions-and-connectors/legacy/update.asciidoc b/docs/api/actions-and-connectors/legacy/update.asciidoc deleted file mode 100644 index dc1468645125c7..00000000000000 --- a/docs/api/actions-and-connectors/legacy/update.asciidoc +++ /dev/null @@ -1,79 +0,0 @@ -[[actions-and-connectors-legacy-api-update]] -==== Legacy Update connector API -++++ -Legacy Update connector -++++ - -deprecated::[7.13.0,Use <> instead.] - -Updates the attributes for an existing connector. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[[actions-and-connectors-legacy-api-update-request]] -===== Request - -`PUT :/api/actions/action/` - -`PUT :/s//api/actions/action/` - -[[actions-and-connectors-legacy-api-update-params]] -===== Path parameters - -`id`:: - (Required, string) The ID of the connector. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[actions-and-connectors-legacy-api-update-request-body]] -===== Request body - -`name`:: - (Required, string) The new name of the connector. - -`config`:: - (Required, object) The new connector configuration. Configuration properties vary depending on the connector type. For information about the configuration properties, refer to <>. - -`secrets`:: - (Required, object) The updated secrets configuration for the connector. Secrets properties vary depending on the connector type. For information about the secrets configuration properties, refer to <>. - -[[actions-and-connectors-legacy-api-update-codes]] -===== Response code - -`200`:: - Indicates a successful call. - -[[actions-and-connectors-legacy-api-update-example]] -===== Example - -[source,sh] --------------------------------------------------- -$ curl -X PUT api/actions/action/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' -{ - "name": "updated-connector", - "config": { - "index": "updated-index" - } -}' --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", - "actionTypeId": ".index", - "name": "updated-connector", - "config": { - "index": "updated-index", - "refresh": false, - "executionTimeField": null - }, - "isPreconfigured": false, - "isDeprecated": false, - "isMissingSecrets": false -} --------------------------------------------------- diff --git a/docs/api/actions-and-connectors/list.asciidoc b/docs/api/actions-and-connectors/list.asciidoc deleted file mode 100644 index 930ba3349537ad..00000000000000 --- a/docs/api/actions-and-connectors/list.asciidoc +++ /dev/null @@ -1,84 +0,0 @@ -[[list-connector-types-api]] -== List connector types API -++++ -List all connector types -++++ - -Retrieves a list of all connector types. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[[list-connector-types-api-request]] -=== {api-request-title} - -`GET :/api/actions/connector_types` - -`GET :/s//api/actions/connector_types` - - -[discrete] -=== {api-prereq-title} - -You do not need any <> to -run this API. - -[[list-connector-types-api-path-params]] -=== {api-path-parms-title} - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[list-connector-types-api-query-params]] -=== {api-query-parms-title} - -`feature_id`:: -(Optional, string) Filters list of connector types to those that support the feature id. - -[[list-connector-types-api-codes]] -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -[[list-connector-types-api-example]] -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -GET api/actions/connector_types --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -[ - { - "id": ".email", <1> - "name": "Email", <2> - "minimum_license_required": "gold", <3> - "enabled": false, <4> - "enabled_in_config": true, <5> - "enabled_in_license": true, <6> - "supported_feature_ids": ["alerting"] <7> - }, - { - "id": ".index", - "name": "Index", - "minimum_license_required": "basic", - "enabled": true, - "enabled_in_config": true, - "enabled_in_license": true, - "supported_feature_ids": ["alerting"] - }, - ... -] --------------------------------------------------- -<1> `id` - The unique ID of the connector type. -<2> `name` - The name of the connector type. -<3> `minimum_license_required` - The license required to use the connector type. -<4> `enabled` - Specifies if the connector type is enabled or disabled in {kib}. -<5> `enabled_in_config` - Specifies if the connector type is enabled or enabled in the {kib} `.yml` file. -<6> `enabled_in_license` - Specifies if the connector type is enabled or disabled in the license. -<7> `supported_feature_ids` - Specifies which Kibana features this connector type supports. \ No newline at end of file diff --git a/docs/api/actions-and-connectors/update.asciidoc b/docs/api/actions-and-connectors/update.asciidoc deleted file mode 100644 index 63c740f712dc25..00000000000000 --- a/docs/api/actions-and-connectors/update.asciidoc +++ /dev/null @@ -1,97 +0,0 @@ -[[update-connector-api]] -== Update connector API -++++ -Update connector -++++ - -Updates the attributes for a connector. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-connectors[connector APIs]. - -[[update-connector-api-request]] -=== {api-request-title} - -`PUT :/api/actions/connector/` - -`PUT :/s//api/actions/connector/` - -[discrete] -=== {api-prereq-title} - -You must have `all` privileges for the *{connectors-feature}* feature in the -*Management* section of the -<>. - -[[update-connector-api-params]] -=== {api-path-parms-title} - -`id`:: - (Required, string) The ID of the connector. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[role="child_attributes"] -[[update-connector-api-request-body]] -=== {api-request-body-title} - -`config`:: -(Required, object) The new connector configuration. Configuration properties -vary depending on the connector type. For example: -+ --- -include::create.asciidoc[tag=connector-config] --- - -`name`:: -(Required, string) The new name of the connector. - -`secrets`:: -(Required^*^, object) The updated secrets configuration for the connector. Secrets -properties vary depending on the connector type. For information about the -secrets configuration properties, refer to -<>. -+ --- -include::create.asciidoc[tag=connector-secrets] --- - -[[update-connector-api-codes]] -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -[[update-connector-api-example]] -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -PUT api/actions/connector/c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad -{ - "name": "updated-connector", - "config": { - "index": "updated-index" - } -} --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", - "connector_type_id": ".index", - "name": "updated-connector", - "config": { - "index": "updated-index", - "refresh": false, - "executionTimeField": null - }, - "is_preconfigured": false, - "is_deprecated": false, - "is_missing_secrets": false -} --------------------------------------------------- diff --git a/docs/api/alerting.asciidoc b/docs/api/alerting.asciidoc index 0e6f2489eb8121..777e3b12b581fd 100644 --- a/docs/api/alerting.asciidoc +++ b/docs/api/alerting.asciidoc @@ -1,19 +1,4 @@ [[alerting-apis]] == Alerting APIs -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. - -include::alerting/create_rule.asciidoc[leveloffset=+1] -include::alerting/delete_rule.asciidoc[leveloffset=+1] -include::alerting/disable_rule.asciidoc[leveloffset=+1] -include::alerting/enable_rule.asciidoc[leveloffset=+1] -include::alerting/find_rules.asciidoc[leveloffset=+1] -include::alerting/health.asciidoc[leveloffset=+1] -include::alerting/get_rules.asciidoc[leveloffset=+1] -include::alerting/list_rule_types.asciidoc[leveloffset=+1] -include::alerting/update_rule.asciidoc[leveloffset=+1] -include::alerting/mute_all_alerts.asciidoc[leveloffset=+1] -include::alerting/mute_alert.asciidoc[leveloffset=+1] -include::alerting/unmute_all_alerts.asciidoc[leveloffset=+1] -include::alerting/unmute_alert.asciidoc[leveloffset=+1] -include::alerting/legacy/index.asciidoc[] +For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. diff --git a/docs/api/alerting/create_rule.asciidoc b/docs/api/alerting/create_rule.asciidoc deleted file mode 100644 index ea6d1d17c28622..00000000000000 --- a/docs/api/alerting/create_rule.asciidoc +++ /dev/null @@ -1,228 +0,0 @@ -[[create-rule-api]] -== Create rule API -++++ -Create rule -++++ - -Create {kib} rules. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[create-rule-api-request]] -=== {api-request-title} - -`POST :/api/alerting/rule/` - -`POST :/s//api/alerting/rule/` - - -=== {api-prereq-title} - -You must have `all` privileges for the appropriate {kib} features, depending on -the `consumer` and `rule_type_id` of the rules you're creating. For example, the -*Management* > *Stack Rules* feature, *Analytics* > *Discover* and *{ml-app}* -features, *{observability}*, and *Security* features. If the rule has `actions`, -you must also have `read` privileges for the *Management* > -*{connectors-feature}* feature. For more details, refer to -<>. - -[[create-rule-api-path-params]] -=== {api-path-parms-title} - -``:: -(Optional, string) Specifies a UUID v1 or v4 to use instead of a randomly -generated ID. - -`space_id`:: -(Optional, string) An identifier for the space. If `space_id` is not provided in -the URL, the default space is used. - -[role="child_attributes"] -[[create-rule-api-request-body]] -=== {api-request-body-title} - -`actions`:: -(Optional, object array) An array of action objects. -+ -.Properties of the action objects: -[%collapsible%open] -===== - -`group`::: -(Required, string) Grouping actions is recommended for escalations for different -types of alerts. If you don't need this, set this value to `default`. - -`id`::: -(Required, string) The ID of the connector saved object, which you can obtain by -using <>. - -`params`::: -(Required, object) The map to the `params` that the -<> will receive. ` params` are handled as Mustache -templates and passed a default set of context. -===== - -`consumer`:: -(Required, string) The name of the application or feature that owns the rule. -For example: `alerts`, `apm`, `discover`, `infrastructure`, `logs`, `metrics`, -`ml`, `monitoring`, `securitySolution`, `siem`, `stackAlerts`, or `uptime`. - -`enabled`:: -(Optional, boolean) Indicates if you want to run the rule on an interval basis -after it is created. - -`name`:: -(Required, string) The name of the rule. While this name does not have to be -unique, a distinctive name can help you identify a rule. - -`notify_when`:: -(Required, string) Defines how often alerts generate actions. Valid values are: -+ --- - -* `onActionGroupChange`: Actions run when the alert status changes. -* `onActiveAlert`: Actions run when the alert becomes active and at each check -interval while the rule conditions are met. -* `onThrottleInterval`: Actions run when the alert becomes active and at the -interval specified in the `throttle` property while the rule conditions are met. - --- - -`params`:: -(Required, object) The parameters to pass to the rule type executor `params` -value. This will also validate against the rule type params validator, if defined. - -`rule_type_id`:: -(Required, string) The ID of the rule type that you want to call when the rule -is scheduled to run. For example, `.es-query`, `.index-threshold`, -`logs.alert.document.count`, `monitoring_alert_cluster_health`, -`siem.thresholdRule`, or `xpack.ml.anomaly_detection_alert`. For more -information, refer to <>. - -`schedule`:: -(Required, object) The check interval, which specifies how frequently the rule -conditions are checked. The interval must be specified in seconds, minutes, -hours or days. For example: `{ "interval": "10s" }`, `{ "interval": "5m" }`, -`{ "interval": "1h" }`, or `{ "interval": "1d" }`. - -`tags`:: -(Optional, string array) A list of tag names that are applied to a rule. - -`throttle`:: -(Optional, string) Defines how often an alert generates repeated actions. -This custom action interval must be specified in seconds, minutes, hours, or -days. For example, `10m` or `1h`. This property is used only if `notify_when` -is `onThrottleInterval`. - -[[create-rule-api-request-codes]] -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -[[create-rule-api-example]] -=== {api-examples-title} - -Create an <> that has actions -associated with a server log connector: - -[source,sh] --------------------------------------------------- -POST api/alerting/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" - }, - "consumer":"alerts", - "rule_type_id":".index-threshold", - "schedule":{ - "interval":"1m" - }, - "actions":[ - { - "id":"dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2", - "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}}" - } - } - ], - "tags":[ - "cpu" - ], - "notify_when":"onActionGroupChange", - "name":"my alert" -} --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id": "41893910-6bca-11eb-9e0d-85d233e3ee35", - "consumer": "alerts", - "tags": ["cpu"], - "name": "my alert", - "enabled": true, - "throttle": null, - "schedule": {"interval": "1m"}, - "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", - "scheduled_task_id": "425b0800-6bca-11eb-9e0d-85d233e3ee35", - "created_by": "elastic", - "updated_by": "elastic", - "created_at": "2022-06-08T17:20:31.632Z", - "updated_at": "2022-06-08T17:20:31.632Z", - "api_key_owner": "elastic", - "notify_when": "onActionGroupChange", - "mute_all": false, - "muted_alert_ids": [], - "execution_status": { - "last_execution_date": "2022-06-08T17:20:31.632Z", - "status": "pending" - }, - "actions": [ - { - "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}}" - }, - "connector_type_id": ".server-log" - } - ] -} --------------------------------------------------- diff --git a/docs/api/alerting/delete_rule.asciidoc b/docs/api/alerting/delete_rule.asciidoc deleted file mode 100644 index 174be0fc756247..00000000000000 --- a/docs/api/alerting/delete_rule.asciidoc +++ /dev/null @@ -1,53 +0,0 @@ -[[delete-rule-api]] -== Delete rule API -++++ -Delete rule -++++ - -Permanently removes a rule. - -WARNING: After you delete a rule, you cannot recover it. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[delete-rule-api-request]] -=== {api-request-title} - -`DELETE :/api/alerting/rule/` - -`DELETE :/s//api/alerting/rule/` - -=== {api-prereq-title} - -You must have `all` privileges for the appropriate {kib} features, depending on -the `consumer` and `rule_type_id` of the rule you're deleting. For example, the -*Management* > *Stack Rules* feature, *Analytics* > *Discover* or *{ml-app}* -features, *{observability}*, or *Security* features. For more details, refer to -<>. - -[[delete-rule-api-path-params]] -=== {api-path-parms-title} - -`id`:: -(Required, string) The identifier of the rule that you want to remove. - -`space_id`:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -[[delete-rule-api-response-codes]] -=== {api-response-codes-title} - -`204`:: -Indicates a successful call. - -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -DELETE api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35 --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/disable_rule.asciidoc b/docs/api/alerting/disable_rule.asciidoc deleted file mode 100644 index 846f9eecb505bf..00000000000000 --- a/docs/api/alerting/disable_rule.asciidoc +++ /dev/null @@ -1,52 +0,0 @@ -[[disable-rule-api]] -== Disable rule API -++++ -Disable rule -++++ - -Disable a rule. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[disable-rule-api-request]] -=== {api-request-title} - -`POST :/api/alerting/rule//_disable` - -`POST :/s//api/alerting/rule//_disable` - - -=== {api-prereq-title} - -You must have `all` privileges for the appropriate {kib} features, depending on -the `consumer` and `rule_type_id` of the rule. For example, -the *Management* > *Stack Rules* feature, *Analytics* > *Discover* and *{ml-app}* -features, *{observability}*, and *Security* features. For more details, refer to -<>. - -[[disable-rule-api-path-params]] -=== {api-path-parms-title} - -`id`:: -(Required, string) The ID of the rule that you want to disable. - -`space_id`:: -(Optional, string) An identifier for the space. If `space_id` is not provided in -the URL, the default space is used. - -[[disable-rule-api-response-codes]] -=== {api-response-codes-title} - -`204`:: -Indicates a successful call. - -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/_disable --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/enable_rule.asciidoc b/docs/api/alerting/enable_rule.asciidoc deleted file mode 100644 index 0d4de7511612e7..00000000000000 --- a/docs/api/alerting/enable_rule.asciidoc +++ /dev/null @@ -1,51 +0,0 @@ -[[enable-rule-api]] -== Enable rule API -++++ -Enable rule -++++ - -Enable a rule. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[enable-rule-api-request]] -=== {api-request-title} - -`POST :/api/alerting/rule//_enable` - -`POST :/s//api/alerting/rule//_enable` - -=== {api-prereq-title} - -You must have `all` privileges for the appropriate {kib} features, depending on -the `consumer` and `rule_type_id` of the rule. For example, the -*Management* > *Stack Rules* feature, *Analytics* > *Discover* and *{ml-app}* -features, *{observability}*, and *Security* features. For more details, refer to -<>. - -[[enable-rule-api-path-params]] -=== {api-path-parms-title} - -`id`:: -(Required, string) The ID of the rule that you want to enable. - -`space_id`:: -(Optional, string) An identifier for the space. If `space_id` is not provided in -the URL, the default space is used. - -[[enable-rule-api-response-codes]] -=== {api-response-codes-title} - -`204`:: -Indicates a successful call. - -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/_enable --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/find_rules.asciidoc b/docs/api/alerting/find_rules.asciidoc deleted file mode 100644 index a7e910475cf36b..00000000000000 --- a/docs/api/alerting/find_rules.asciidoc +++ /dev/null @@ -1,166 +0,0 @@ -[[find-rules-api]] -== Find rules API -++++ -Find rules -++++ - -Retrieve a paginated set of rules based on condition. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[find-rules-api-request]] -=== {api-request-title} - -`GET :/api/alerting/rules/_find` - -`GET :/s//api/alerting/rules/_find` - -=== {api-prereq-title} - -You must have `read` privileges for the appropriate {kib} features, depending on -the `consumer` and `rule_type_id` of the rules you're seeking. For example, the -*Management* > *Stack Rules* feature, *Analytics* > *Discover* and *{ml-app}* -features, *{observability}*, and *Security* features. To find rules associated -with the *{stack-monitor-app}* feature, use the `monitoring_user` built-in role. - -For more details, refer to <>. - -=== {api-description-title} - -As rules change in {kib}, the results on each page of the response also change. -Use the find API for traditional paginated results, but avoid using it to export -large amounts of data. - -NOTE: Rule `params` are stored as a {ref}/flattened.html[flattened field type] -and analyzed as keywords. - -[[find-rules-api-path-params]] -=== {api-path-parms-title} - -`space_id`:: -(Optional, string) An identifier for the space. If `space_id` is not provided in -the URL, the default space is used. - -[[find-rules-api-query-params]] -=== {api-query-parms-title} - -`default_search_operator`:: -(Optional, string) The operator to use for the `simple_query_string`. The -default is 'OR'. - -`fields`:: -(Optional, array of strings) The fields to return in the `attributes` key of the -response. - -`filter`:: -(Optional, string) A <> string that you filter with an -attribute from your saved object. It should look like -`savedObjectType.attributes.title: "myTitle"`. However, If you used a direct -attribute of a saved object, such as `updatedAt`, you will have to define your -filter, for example, `savedObjectType.updatedAt > 2018-12-22`. - -`has_reference`:: -(Optional, object) Filters the rules that have a relation with the reference -objects with the specific "type" and "ID". - -`page`:: -(Optional, number) The page number. - -`per_page`:: -(Optional, number) The number of rules to return per page. - -`search`:: -(Optional, string) An {es} -{ref}/query-dsl-simple-query-string-query.html[simple_query_string] query that -filters the rules in the response. - -`search_fields`:: -(Optional, array or string) The fields to perform the `simple_query_string` -parsed query against. - -`sort_field`:: -(Optional, string) Sorts the response. Could be a rule field returned in the -`attributes` key of the response. - -`sort_order`:: -(Optional, string) Sort direction, either `asc` or `desc`. - -[[find-rules-api-request-codes]] -=== {api-response-codes-title} - -`200`:: -Indicates a successful call. - -=== {api-examples-title} - -Find rules with names that start with `my`: - -[source,sh] --------------------------------------------------- -GET api/alerting/rules/_find?search_fields=name&search=my* --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "page": 1, - "total": 1, - "per_page": 10, - "data": [{ - "id": "b530fed0-74f5-11ed-9801-35303b735aef", - "name": "cluster_health_rule", - "consumer": "alerts", - "enabled": true, - "tags": ["cluster","health"], - "throttle": null, - "schedule": {"interval":"1m"}, - "params": {}, - "rule_type_id": "monitoring_alert_cluster_health", - "created_by": "elastic", - "updated_by": "elastic", - "created_at": "2022-12-05T23:36:58.284Z", - "updated_at": "2022-12-05T23:36:58.284Z", - "api_key_owner": "elastic", - "notify_when": "onActiveAlert", - "mute_all": false, - "muted_alert_ids": [], - "scheduled_task_id": "b530fed0-74f5-11ed-9801-35303b735aef", - "execution_status": { - "status": "ok", - "last_execution_date": "2022-12-06T00:09:31.882Z", - "last_duration": 42 - }, - "actions": [{ - "group": "default", - "id": "9dca3e00-74f5-11ed-9801-35303b735aef", - "params": { - "level": "info", - "message": "{{context.internalFullMessage}}" - }, - "connector_type_id": ".server-log" - }], - "last_run":{ - "alerts_count": {"new": 0,"ignored": 0,"recovered": 0,"active": 0}, - "outcome_msg": null, - "warning": null, - "outcome": "succeeded" - }, - "next_run": "2022-12-06T00:10:31.811Z" - }] -} --------------------------------------------------- - -For parameters that accept multiple values (such as `fields`), repeat the -query parameter for each value: - -[source,sh] --------------------------------------------------- -GET api/alerting/rules/_find?fields=id&fields=name --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/get_rules.asciidoc b/docs/api/alerting/get_rules.asciidoc deleted file mode 100644 index 386d2ebe3404d7..00000000000000 --- a/docs/api/alerting/get_rules.asciidoc +++ /dev/null @@ -1,122 +0,0 @@ -[[get-rule-api]] -== Get rule API -++++ -Get rule -++++ - -Retrieve a rule by ID. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[get-rule-api-request]] -=== {api-request-title} - -`GET :/api/alerting/rule/` - -`GET :/s//api/alerting/rule/` - -=== {api-prereq-title} - -You must have `read` privileges for the appropriate {kib} features, depending on -the `consumer` and `rule_type_id` of the rules you're seeking. For example, the -*Management* > *Stack Rules* feature, *Analytics* > *Discover* and *{ml-app}* -features, *{observability}*, and *Security* features. To get rules associated -with the *{stack-monitor-app}* feature, use the `monitoring_user` built-in role. - -For more details, refer to <>. - -[[get-rule-api-params]] -=== {api-path-parms-title} - -`id`:: -(Required, string) The identifier of the rule to retrieve. - -`space_id`:: -(Optional, string) An identifier for the space. If `space_id` is not provided in -the URL, the default space is used. - -[[get-rule-api-codes]] -=== {api-response-codes-title} - -`200`:: -Indicates a successful call. - -[[get-rule-api-example]] -=== {api-examples-title} - -Retrieve the rule object with the ID `41893910-6bca-11eb-9e0d-85d233e3ee35`: - -[source,sh] --------------------------------------------------- -GET api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35 --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id":"31697a40-7b36-11ed-aa79-f742c05329b2", - "consumer":"alerts", - "tags":["cpu"], - "name":"my alert", - "enabled":true, - "throttle":null, - "schedule":{ - "interval":"1m" - }, - "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", - "created_by":"elastic", - "updated_by":"elastic", - "created_at":"2022-12-13T22:33:41.163Z", - "updated_at":"2022-12-13T22:33:41.163Z", - "api_key_owner":"elastic", - "notify_when":"onActionGroupChange", - "muted_alert_ids":[], - "mute_all":false, - "scheduled_task_id":"31697a40-7b36-11ed-aa79-f742c05329b2", - "execution_status":{ - "status":"ok", - "last_execution_date":"2022-12-13T22:33:44.388Z", - "last_duration":83 - }, - "actions":[{ - "group":"threshold met", - "id":"1007a0c0-7a6e-11ed-89d5-abec321c0def", - "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}}" - }, - "connector_type_id":".server-log" - }], - "last_run":{ - "alerts_count":{ - "new":0, - "ignored":0, - "recovered":0, - "active":0 - }, - "outcome_msg":null, - "warning":null, - "outcome":"succeeded" - }, - "next_run":"2022-12-13T22:34:44.314Z" -} --------------------------------------------------- diff --git a/docs/api/alerting/health.asciidoc b/docs/api/alerting/health.asciidoc deleted file mode 100644 index 6652b3e930ec6f..00000000000000 --- a/docs/api/alerting/health.asciidoc +++ /dev/null @@ -1,82 +0,0 @@ -[[get-alerting-framework-health-api]] -== Get alerting framework health API -++++ -Get alerting framework health -++++ - -Retrieve the health status of the alerting framework. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[get-alerting-framework-health-api-request]] -=== {api-request-title} - -`GET :/api/alerting/_health` - -`GET :/s//api/alerting/_health` - -=== {api-prereq-title} - -You must have `read` privileges for the *Management* > *Stack Rules* feature or -for at least one of the *Analytics* > *Discover*, *Analytics* > *{ml-app}*, -*{observability}*, or *Security* features. - -[[get-alerting-framework-health-api-params]] -=== {api-path-parms-title} - -`space_id`:: -(Optional, string) An identifier for the space. If `space_id` is not provided in -the URL, the default space is used. - -[[get-alerting-framework-health-api-codes]] -=== {api-response-codes-title} - -`200`:: -Indicates a successful call. - -[[get-alerting-framework-health-api-example]] -=== {api-examples-title} - -Retrieve the health status of the alerting framework: - -[source,sh] --------------------------------------------------- -GET api/alerting/_health --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "is_sufficiently_secure":true, <1> - "has_permanent_encryption_key":true, <2> - "alerting_framework_health":{ <3> - "decryption_health":{ - "status":"ok", - "timestamp":"2022-06-21T21:46:15.023Z" - }, - "execution_health":{ - "status":"ok", - "timestamp":"2022-06-21T21:46:15.023Z" - }, - "read_health":{ - "status":"ok", - "timestamp":"2022-06-21T21:46:15.023Z" - } - } -} --------------------------------------------------- -<1> `is_sufficiently_secure` is `false` when security is enabled, but TLS is not. -<2> `has_permanent_encryption_key` is `false` when the encrypted saved object -plugin does not have a permanent encryption key. -<3> `alerting_framework_health` has three substates that identify the health of -the alerting framework: `decryption_health`, `execution_health`, and -`read_health`. `decryption_health` returns the timestamp and status of the rule -decryption: `ok`, `warn` or `error`. `execution_health` returns the timestamp -and status of the rule execution: `ok`, `warn` or `error`. `read_health` returns -the timestamp and status of the rule reading events: `ok`, `warn` or `error`. diff --git a/docs/api/alerting/legacy/create.asciidoc b/docs/api/alerting/legacy/create.asciidoc deleted file mode 100644 index 333690e38ec936..00000000000000 --- a/docs/api/alerting/legacy/create.asciidoc +++ /dev/null @@ -1,207 +0,0 @@ -[[alerts-api-create]] -=== Legacy create alert API -++++ -Legacy create alert -++++ - -deprecated::[7.13.0,Use <> instead.] - -Create {kib} alerts. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-create-request]] -==== Request - -`POST :/api/alerts/alert/` - -`POST :/s//api/alerts/alert/` - -[[alerts-api-create-path-params]] -==== Path parameters - -``:: - (Optional, string) Specifies a UUID v1 or v4 to use instead of a randomly generated ID. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-create-request-body]] -==== Request body - -`name`:: - (Required, string) A name to reference and search. - -`tags`:: - (Optional, string array) A list of keywords to reference and search. - -`alertTypeId`:: - (Required, string) The ID of the alert type that you want to call when the alert is scheduled to run. - -`schedule`:: - (Required, object) The schedule specifying when this alert should be run, using one of the available schedule formats specified under -+ -._Schedule Formats_. -[%collapsible%open] -===== -A schedule is structured such that the key specifies the format you wish to use and its value specifies the schedule. - -We currently support the _Interval format_ which specifies the interval in seconds, minutes, hours or days at which the alert should execute. -Example: `{ interval: "10s" }`, `{ interval: "5m" }`, `{ interval: "1h" }`, `{ interval: "1d" }`. - -There are plans to support multiple other schedule formats in the near future. -===== - -`throttle`:: - (Optional, string) How often this alert should fire the same actions. This will prevent the alert from sending out the same notification over and over. For example, if an alert with a `schedule` of 1 minute stays in a triggered state for 90 minutes, setting a `throttle` of `10m` or `1h` will prevent it from sending 90 notifications during this period. - -`notifyWhen`:: - (Required, string) The condition for throttling the notification: `onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`. - -`enabled`:: - (Optional, boolean) Indicates if you want to run the alert on an interval basis after it is created. - -`consumer`:: - (Required, string) The name of the application that owns the alert. This name has to match the Kibana Feature name, as that dictates the required RBAC privileges. - -`params`:: - (Required, object) The parameters to pass to the alert type executor `params` value. This will also validate against the alert type params validator, if defined. - -`actions`:: - (Optional, object array) An array of the following action objects. -+ -.Properties of the action objects: -[%collapsible%open] -===== - `group`::: - (Required, string) Grouping actions is recommended for escalations for different types of alert instances. If you don't need this, set this value to `default`. - - `id`::: - (Required, string) The ID of the action saved object to execute. - - `actionTypeId`::: - (Required, string) The ID of the <>. - - `params`::: - (Required, object) The map to the `params` that the <> will receive. ` params` are handled as Mustache templates and passed a default set of context. -===== - - -[[alerts-api-create-request-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -[[alerts-api-create-example]] -==== Example - -[source,sh] --------------------------------------------------- -$ curl -X POST api/alerts/alert -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' -{ - "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" - }, - "consumer":"alerts", - "alertTypeId":".index-threshold", - "schedule":{ - "interval":"1m" - }, - "actions":[ - { - "id":"dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2", - "actionTypeId":".server-log", - "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}}" - } - } - ], - "tags":[ - "cpu" - ], - "notifyWhen":"onActionGroupChange", - "name":"my alert" -}' --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id": "41893910-6bca-11eb-9e0d-85d233e3ee35", - "notifyWhen": "onActionGroupChange", - "params": { - "aggType": "avg", - "termSize": 6, - "thresholdComparator": ">", - "timeWindowSize": 5, - "timeWindowUnit": "m", - "groupBy": "top", - "threshold": [ - 1000 - ], - "index": [ - ".kibana" - ], - "timeField": "@timestamp", - "aggField": "sheet.version", - "termField": "name.keyword" - }, - "consumer": "alerts", - "alertTypeId": ".index-threshold", - "schedule": { - "interval": "1m" - }, - "actions": [ - { - "actionTypeId": ".server-log", - "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}}" - }, - "id": "dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2" - } - ], - "tags": [ - "cpu" - ], - "name": "my alert", - "enabled": true, - "throttle": null, - "apiKeyOwner": "elastic", - "createdBy": "elastic", - "updatedBy": "elastic", - "muteAll": false, - "mutedInstanceIds": [], - "updatedAt": "2021-02-10T18:03:19.961Z", - "createdAt": "2021-02-10T18:03:19.961Z", - "scheduledTaskId": "425b0800-6bca-11eb-9e0d-85d233e3ee35", - "executionStatus": { - "lastExecutionDate": "2021-02-10T18:03:19.966Z", - "status": "pending" - } -} --------------------------------------------------- diff --git a/docs/api/alerting/legacy/delete.asciidoc b/docs/api/alerting/legacy/delete.asciidoc deleted file mode 100644 index bbd350c627e02a..00000000000000 --- a/docs/api/alerting/legacy/delete.asciidoc +++ /dev/null @@ -1,48 +0,0 @@ -[[alerts-api-delete]] -=== Legacy delete alert API -++++ -Legacy delete alert -++++ - -deprecated::[7.13.0,Use <> instead.] - -Permanently remove an alert. - -WARNING: Once you delete an alert, you cannot recover it. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-delete-request]] -==== Request - -`DELETE :/api/alerts/alert/` - -`DELETE :/s//api/alerts/alert/` - -[[alerts-api-delete-path-params]] -==== Path parameters - -`id`:: - (Required, string) The ID of the alert that you want to remove. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-delete-response-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -==== Example - -Delete an alert with ID: - -[source,sh] --------------------------------------------------- -$ curl -X DELETE api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35 --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/legacy/disable.asciidoc b/docs/api/alerting/legacy/disable.asciidoc deleted file mode 100644 index 1a30a7ed3d1c4e..00000000000000 --- a/docs/api/alerting/legacy/disable.asciidoc +++ /dev/null @@ -1,46 +0,0 @@ -[[alerts-api-disable]] -=== Legacy disable alert API -++++ -Legacy disable alert -++++ - -deprecated::[7.13.0,Use <> instead.] - -Disable an alert. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-disable-request]] -==== Request - -`POST :/api/alerts/alert//_disable` - -`POST :/s//api/alerts/alert//_disable` - -[[alerts-api-disable-path-params]] -==== Path parameters - -`id`:: - (Required, string) The ID of the alert that you want to disable. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-disable-response-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -==== Example - -Disable an alert with ID: - -[source,sh] --------------------------------------------------- -$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/_disable --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/legacy/enable.asciidoc b/docs/api/alerting/legacy/enable.asciidoc deleted file mode 100644 index 3be2217e60604d..00000000000000 --- a/docs/api/alerting/legacy/enable.asciidoc +++ /dev/null @@ -1,46 +0,0 @@ -[[alerts-api-enable]] -=== Legacy enable alert API -++++ -Legacy enable alert -++++ - -deprecated::[7.13.0,Use <> instead.] - -Enable an alert. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-enable-request]] -==== Request - -`POST :/api/alerts/alert//_enable` - -`POST :/s//api/alerts/alert//_enable` - -[[alerts-api-enable-path-params]] -==== Path parameters - -`id`:: - (Required, string) The ID of the alert that you want to enable. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-enable-response-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -==== Example - -Enable an alert with ID: - -[source,sh] --------------------------------------------------- -$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/_enable --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/legacy/find.asciidoc b/docs/api/alerting/legacy/find.asciidoc deleted file mode 100644 index da2f24737d41bc..00000000000000 --- a/docs/api/alerting/legacy/find.asciidoc +++ /dev/null @@ -1,134 +0,0 @@ -[[alerts-api-find]] -=== Legacy find alerts API -++++ -Legacy find alerts -++++ - -deprecated::[7.13.0,Use <> instead.] - -Retrieve a paginated set of alerts based on condition. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -As alerts change in {kib}, the results on each page of the response also -change. Use the find API for traditional paginated results, but avoid using it to export large amounts of data. - -[[alerts-api-find-request]] -==== Request - -`GET :/api/alerts/_find` - -`GET :/s//api/alerts/_find` - -[[alerts-api-find-path-params]] -==== Path parameters - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-find-query-params]] -==== Query Parameters - -NOTE: Alert `params` are stored as a {ref}/flattened.html[flattened field type] and analyzed as keywords. - -`per_page`:: - (Optional, number) The number of alerts to return per page. - -`page`:: - (Optional, number) The page number. - -`search`:: - (Optional, string) An Elasticsearch {ref}/query-dsl-simple-query-string-query.html[simple_query_string] query that filters the alerts in the response. - -`default_search_operator`:: - (Optional, string) The operator to use for the `simple_query_string`. The default is 'OR'. - -`search_fields`:: - (Optional, array|string) The fields to perform the `simple_query_string` parsed query against. - -`fields`:: - (Optional, array|string) The fields to return in the `attributes` key of the response. - -`sort_field`:: - (Optional, string) Sorts the response. Could be an alert fields returned in the `attributes` key of the response. - -`sort_order`:: - (Optional, string) Sort direction, either `asc` or `desc`. - -`has_reference`:: - (Optional, object) Filters the alerts that have a relations with the reference objects with the specific "type" and "ID". - -`filter`:: - (Optional, string) A <> string that you filter with an attribute from your saved object. - It should look like savedObjectType.attributes.title: "myTitle". However, If you used a direct attribute of a saved object, such as `updatedAt`, - you will have to define your filter, for example, savedObjectType.updatedAt > 2018-12-22. - -[[alerts-api-find-request-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -==== Examples - -Find alerts with names that start with `my`: - -[source,sh] --------------------------------------------------- -$ curl -X GET api/alerts/_find?search_fields=name&search=my* --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "page": 1, - "perPage": 10, - "total": 1, - "data": [ - { - "id": "0a037d60-6b62-11eb-9e0d-85d233e3ee35", - "notifyWhen": "onActionGroupChange", - "params": { - "aggType": "avg", - }, - "consumer": "alerts", - "alertTypeId": "test.alert.type", - "schedule": { - "interval": "1m" - }, - "actions": [], - "tags": [], - "name": "test alert", - "enabled": true, - "throttle": null, - "apiKeyOwner": "elastic", - "createdBy": "elastic", - "updatedBy": "elastic", - "muteAll": false, - "mutedInstanceIds": [], - "updatedAt": "2021-02-10T05:37:19.086Z", - "createdAt": "2021-02-10T05:37:19.086Z", - "scheduledTaskId": "0b092d90-6b62-11eb-9e0d-85d233e3ee35", - "executionStatus": { - "lastExecutionDate": "2021-02-10T17:55:14.262Z", - "status": "ok" - } - }, - ] -} --------------------------------------------------- - -For parameters that accept multiple values (e.g. `fields`), repeat the -query parameter for each value: - -[source,sh] --------------------------------------------------- -$ curl -X GET api/alerts/_find?fields=id&fields=name --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/legacy/get.asciidoc b/docs/api/alerting/legacy/get.asciidoc deleted file mode 100644 index 98e6f1a9ed1f3d..00000000000000 --- a/docs/api/alerting/legacy/get.asciidoc +++ /dev/null @@ -1,82 +0,0 @@ -[[alerts-api-get]] -=== Legacy get alert API -++++ -Legacy get alert -++++ - -deprecated::[7.13.0,Use <> instead.] - -Retrieve an alert by ID. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-get-request]] -==== Request - -`GET :/api/alerts/alert/` - -`GET :/s//api/alerts/alert/` - -[[alerts-api-get-params]] -==== Path parameters - -`id`:: - (Required, string) The ID of the alert to retrieve. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-get-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -[[alerts-api-get-example]] -==== Example - -Retrieve the alert object with the ID `41893910-6bca-11eb-9e0d-85d233e3ee35`: - -[source,sh] --------------------------------------------------- -$ curl -X GET api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35 --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id": "0a037d60-6b62-11eb-9e0d-85d233e3ee35", - "notifyWhen": "onActionGroupChange", - "params": { - "aggType": "avg", - }, - "consumer": "alerts", - "alertTypeId": "test.alert.type", - "schedule": { - "interval": "1m" - }, - "actions": [], - "tags": [], - "name": "test alert", - "enabled": true, - "throttle": null, - "apiKeyOwner": "elastic", - "createdBy": "elastic", - "updatedBy": "elastic", - "muteAll": false, - "mutedInstanceIds": [], - "updatedAt": "2021-02-10T05:37:19.086Z", - "createdAt": "2021-02-10T05:37:19.086Z", - "scheduledTaskId": "0b092d90-6b62-11eb-9e0d-85d233e3ee35", - "executionStatus": { - "lastExecutionDate": "2021-02-10T17:55:14.262Z", - "status": "ok" - } -} --------------------------------------------------- diff --git a/docs/api/alerting/legacy/health.asciidoc b/docs/api/alerting/legacy/health.asciidoc deleted file mode 100644 index 6a4af96966769f..00000000000000 --- a/docs/api/alerting/legacy/health.asciidoc +++ /dev/null @@ -1,100 +0,0 @@ -[[alerts-api-health]] -=== Legacy get Alerting framework health API -++++ -Legacy get Alerting framework health -++++ - -deprecated::[7.13.0,Use <> instead.] - -Retrieve the health status of the Alerting framework. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-health-request]] -==== Request - -`GET :/api/alerts/_health` - -`GET :/s//api/alerts/_health` - -[[alerts-api-health-params]] -==== Path parameters - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-health-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -[[alerts-api-health-example]] -==== Example - -Retrieve the health status of the Alerting framework: - -[source,sh] --------------------------------------------------- -$ curl -X GET api/alerts/_health --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "isSufficientlySecure":true, - "hasPermanentEncryptionKey":true, - "alertingFrameworkHealth":{ - "decryptionHealth":{ - "status":"ok", - "timestamp":"2021-02-10T23:35:04.949Z" - }, - "executionHealth":{ - "status":"ok", - "timestamp":"2021-02-10T23:35:04.949Z" - }, - "readHealth":{ - "status":"ok", - "timestamp":"2021-02-10T23:35:04.949Z" - } - } -} --------------------------------------------------- - -The health API response contains the following properties: - -[cols="2*<"] -|=== - -| `isSufficientlySecure` -| Returns `false` if security is enabled, but TLS is not. - -| `hasPermanentEncryptionKey` -| Return the state `false` if Encrypted Saved Object plugin has not a permanent encryption Key. - -| `alertingFrameworkHealth` -| This state property has three substates that identify the health of the alerting framework API: `decryptionHealth`, `executionHealth`, and `readHealth`. - -|=== - -`alertingFrameworkHealth` consists of the following properties: - -[cols="2*<"] -|=== - -| `decryptionHealth` -| Returns the timestamp and status of the alert decryption: `ok`, `warn` or `error` . - -| `executionHealth` -| Returns the timestamp and status of the alert execution: `ok`, `warn` or `error`. - -| `readHealth` -| Returns the timestamp and status of the alert reading events: `ok`, `warn` or `error`. - -|=== diff --git a/docs/api/alerting/legacy/index.asciidoc b/docs/api/alerting/legacy/index.asciidoc deleted file mode 100644 index 48f37c06ff5436..00000000000000 --- a/docs/api/alerting/legacy/index.asciidoc +++ /dev/null @@ -1,18 +0,0 @@ -[[alerts-api]] -=== Deprecated 7.x APIs - -These APIs are deprecated and will be removed in a future release. - -include::create.asciidoc[leveloffset=+1] -include::delete.asciidoc[leveloffset=+1] -include::disable.asciidoc[leveloffset=+1] -include::enable.asciidoc[leveloffset=+1] -include::find.asciidoc[leveloffset=+1] -include::get.asciidoc[leveloffset=+1] -include::health.asciidoc[leveloffset=+1] -include::list.asciidoc[leveloffset=+1] -include::mute.asciidoc[leveloffset=+1] -include::mute_all.asciidoc[leveloffset=+1] -include::unmute.asciidoc[leveloffset=+1] -include::unmute_all.asciidoc[leveloffset=+1] -include::update.asciidoc[leveloffset=+1] \ No newline at end of file diff --git a/docs/api/alerting/legacy/list.asciidoc b/docs/api/alerting/legacy/list.asciidoc deleted file mode 100644 index cdcd7832a31eda..00000000000000 --- a/docs/api/alerting/legacy/list.asciidoc +++ /dev/null @@ -1,146 +0,0 @@ -[[alerts-api-list]] -=== Legacy list alert types API -++++ -Legacy list all alert types -++++ - -deprecated::[7.13.0,Use <> instead.] - -Retrieve a list of all alert types. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-list-request]] -==== Request - -`GET :/api/alerts/list_alert_types` - -`GET :/s//api/alerts/list_alert_types` - -[[alerts-api-list-params]] -==== Path parameters - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-list-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -[[alerts-api-list-example]] -==== Example - -[source,sh] --------------------------------------------------- -$ curl -X GET api/alerts/list_alert_types --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -[ - { - "id":".index-threshold", - "name":"Index threshold", - "actionGroups":[ - { - "id":"threshold met", - "name":"Threshold met" - }, - { - "id":"recovered", - "name":"Recovered" - } - ], - "recoveryActionGroup":{ - "id":"recovered", - "name":"Recovered" - }, - "defaultActionGroupId":"threshold met", - "actionVariables":{ - "context":[ - { - "name":"message", - "description":"A pre-constructed message for the alert." - }, - ], - "state":[], - "params":[ - { - "name":"threshold", - "description":"An array of values to use as the threshold; 'between' and 'notBetween' require two values, the others require one." - }, - { - "name":"index", - "description":"index" - }, - ] - }, - "producer":"stackAlerts", - "minimumLicenseRequired":"basic", - "isExportable":true, - "enabledInLicense":true, - "authorizedConsumers":{ - "alerts":{ - "read":true, - "all":true - }, - "stackAlerts":{ - "read":true, - "all":true - }, - "uptime":{ - "read":true, - "all":true - } - } - } -] --------------------------------------------------- - -Each alert type contains the following properties: - -[cols="2*<"] -|=== - -| `name` -| The descriptive name of the alert type. - -| `id` -| The unique ID of the alert type. - -| `minimumLicenseRequired` -| The license required to use the alert type. - -| `isExportable` -| Whether the rule type is exportable through the Saved Objects Management UI. - -| `enabledInLicense` -| Whether the alert type is enabled or disabled based on the license. - -| `actionGroups` -| An explicit list of groups for which the alert type can schedule actions, each with the action group's unique ID and human readable name. Alert `actions` validation will use this configuration to ensure that groups are valid. Use `kbn-i18n` to translate the names of the action group when registering the alert type. - -| `recoveryActionGroup` -| An action group to use when an alert instance goes from an active state, to an inactive one. Do not specify this action group under the `actionGroups` property. If `recoveryActionGroup` is not specified, the default `recovered` action group is used. - -| `defaultActionGroupId` -| The default ID for the alert type group. - -| `actionVariables` -| An explicit list of action variables that the alert type makes available via context and state in action parameter templates, and a short human readable description. The Alert UI will use this information to prompt users for these variables in action parameter editors. Use `kbn-i18n` to translate the descriptions. - -| `producer` -| The ID of the application producing this alert type. - -| `authorizedConsumers` -| The list of the plugins IDs that have access to the alert type. - -|=== diff --git a/docs/api/alerting/legacy/mute.asciidoc b/docs/api/alerting/legacy/mute.asciidoc deleted file mode 100644 index 6486cb484003fd..00000000000000 --- a/docs/api/alerting/legacy/mute.asciidoc +++ /dev/null @@ -1,49 +0,0 @@ -[[alerts-api-mute]] -=== Legacy mute alert instance API -++++ -Legacy mute alert instance -++++ - -deprecated::[7.13.0,Use <> instead.] - -Mute an alert instance. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-mute-request]] -==== Request - -`POST :/api/alerts/alert//alert_instance//_mute` - -`POST :/s//api/alerts/alert//alert_instance//_mute` - -[[alerts-api-mute-path-params]] -==== Path parameters - -`id`:: - (Required, string) The ID of the alert whose instance you want to mute. - -`alert_instance_id`:: - (Required, string) The ID of the alert instance that you want to mute. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-mute-response-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -==== Example - -Mute alert instance with ID: - -[source,sh] --------------------------------------------------- -$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/alert_instance/dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2/_mute --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/legacy/mute_all.asciidoc b/docs/api/alerting/legacy/mute_all.asciidoc deleted file mode 100644 index 19182469426e59..00000000000000 --- a/docs/api/alerting/legacy/mute_all.asciidoc +++ /dev/null @@ -1,46 +0,0 @@ -[[alerts-api-mute-all]] -=== Legacy mute all alert instances API -++++ -Legacy mute all alert instances -++++ - -deprecated::[7.13.0,Use <> instead.] - -Mute all alert instances. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-mute-all-request]] -==== Request - -`POST :/api/alerts/alert//_mute_all` - -`POST :/s//api/alerts/alert//_mute_all` - -[[alerts-api-mute-all-path-params]] -==== Path parameters - -`id`:: - (Required, string) The ID of the alert whose instances you want to mute. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-mute-all-response-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -==== Example - -Mute all alert instances with ID: - -[source,sh] --------------------------------------------------- -$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/_mute_all --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/legacy/unmute.asciidoc b/docs/api/alerting/legacy/unmute.asciidoc deleted file mode 100644 index 9f15002573d011..00000000000000 --- a/docs/api/alerting/legacy/unmute.asciidoc +++ /dev/null @@ -1,49 +0,0 @@ -[[alerts-api-unmute]] -=== Legacy unmute alert instance API -++++ -Legacy unmute alert instance -++++ - -deprecated::[7.13.0,Use <> instead.] - -Unmute an alert instance. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-unmute-request]] -==== Request - -`POST :/api/alerts/alert//alert_instance//_unmute` - -`POST :/s//api/alerts/alert//alert_instance//_unmute` - -[[alerts-api-unmute-path-params]] -==== Path parameters - -`id`:: - (Required, string) The ID of the alert whose instance you want to mute.. - -`alert_instance_id`:: - (Required, string) The ID of the alert instance that you want to unmute. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-unmute-response-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -==== Example - -Unmute alert instance with ID: - -[source,sh] --------------------------------------------------- -$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/alert_instance/dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2/_unmute --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/legacy/unmute_all.asciidoc b/docs/api/alerting/legacy/unmute_all.asciidoc deleted file mode 100644 index 8d218da184eabe..00000000000000 --- a/docs/api/alerting/legacy/unmute_all.asciidoc +++ /dev/null @@ -1,46 +0,0 @@ -[[alerts-api-unmute-all]] -=== Legacy unmute all alert instances API -++++ -Legacy unmute all alert instances -++++ - -deprecated::[7.13.0,Use <> instead.] - -Unmute all alert instances. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-unmute-all-request]] -==== Request - -`POST :/api/alerts/alert//_unmute_all` - -`POST :/s//api/alerts/alert//_unmute_all` - -[[alerts-api-unmute-all-path-params]] -==== Path parameters - -`id`:: - (Required, string) The ID of the alert whose instances you want to unmute. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-unmute-all-response-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -==== Example - -Unmute all alert instances with ID: - -[source,sh] --------------------------------------------------- -$ curl -X POST api/alerts/alert/41893910-6bca-11eb-9e0d-85d233e3ee35/_unmute_all --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/legacy/update.asciidoc b/docs/api/alerting/legacy/update.asciidoc deleted file mode 100644 index 880132d40c2523..00000000000000 --- a/docs/api/alerting/legacy/update.asciidoc +++ /dev/null @@ -1,146 +0,0 @@ -[[alerts-api-update]] -=== Legacy update alert API -++++ -Legacy update alert -++++ - -deprecated::[7.13.0,Use <> instead.] - -Update the attributes for an existing alert. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[alerts-api-update-request]] -==== Request - -`PUT :/api/alerts/alert/` - -`PUT :/s//api/alerts/alert/` - -[[alerts-api-update-path-params]] -==== Path parameters - -`id`:: - (Required, string) The ID of the alert that you want to update. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[alerts-api-update-request-body]] -==== Request body - -`name`:: - (Required, string) A name to reference and search. - -`tags`:: - (Optional, string array) A list of keywords to reference and search. - -`schedule`:: - (Required, object) When to run this alert. Use one of the available schedule formats. -+ -._Schedule Formats_. -[%collapsible%open] -===== -A schedule uses a key: value format. {kib} currently supports the _Interval format_ , which specifies the interval in seconds, minutes, hours, or days at which to execute the alert. - -Example: `{ interval: "10s" }`, `{ interval: "5m" }`, `{ interval: "1h" }`, `{ interval: "1d" }`. - -===== - -`throttle`:: - (Optional, string) How often this alert should fire the same actions. This will prevent the alert from sending out the same notification over and over. For example, if an alert with a `schedule` of 1 minute stays in a triggered state for 90 minutes, setting a `throttle` of `10m` or `1h` will prevent it from sending 90 notifications during this period. - -`notifyWhen`:: - (Required, string) The condition for throttling the notification: `onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`. - -`params`:: - (Required, object) The parameters to pass to the alert type executor `params` value. This will also validate against the alert type params validator, if defined. - -`actions`:: - (Optional, object array) An array of the following action objects. -+ -.Properties of the action objects: -[%collapsible%open] -===== - `group`::: - (Required, string) Grouping actions is recommended for escalations for different types of alert instances. If you don't need this, set the value to `default`. - - `id`::: - (Required, string) The ID of the action that saved object executes. - - `actionTypeId`::: - (Required, string) The id of the <>. - - `params`::: - (Required, object) The map to the `params` that the <> will receive. `params` are handled as Mustache templates and passed a default set of context. -===== - - -[[alerts-api-update-errors-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -[[alerts-api-update-example]] -==== Example - -Update an alert with ID `ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74` with a different name: - -[source,sh] --------------------------------------------------- -$ curl -X PUT api/alerts/alert/ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 - -{ - "notifyWhen": "onActionGroupChange", - "params": { - "aggType": "avg", - }, - "schedule": { - "interval": "1m" - }, - "actions": [], - "tags": [], - "name": "new name", - "throttle": null, -} --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id": "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74", - "notifyWhen": "onActionGroupChange", - "params": { - "aggType": "avg", - }, - "consumer": "alerts", - "alertTypeId": "test.alert.type", - "schedule": { - "interval": "1m" - }, - "actions": [], - "tags": [], - "name": "new name", - "enabled": true, - "throttle": null, - "apiKeyOwner": "elastic", - "createdBy": "elastic", - "updatedBy": "elastic", - "muteAll": false, - "mutedInstanceIds": [], - "updatedAt": "2021-02-10T05:37:19.086Z", - "createdAt": "2021-02-10T05:37:19.086Z", - "scheduledTaskId": "0b092d90-6b62-11eb-9e0d-85d233e3ee35", - "executionStatus": { - "lastExecutionDate": "2021-02-10T17:55:14.262Z", - "status": "ok" - } -} --------------------------------------------------- diff --git a/docs/api/alerting/list_rule_types.asciidoc b/docs/api/alerting/list_rule_types.asciidoc deleted file mode 100644 index abf6e3b9fe36fc..00000000000000 --- a/docs/api/alerting/list_rule_types.asciidoc +++ /dev/null @@ -1,204 +0,0 @@ -[[list-rule-types-api]] -== Get rule types API -++++ -Get rule types -++++ - -Retrieve a list of rule types that the user is authorized to access. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - - -[[list-rule-types-api-request]] -=== {api-request-title} - -`GET :/api/alerting/rule_types` - -`GET :/s//api/alerting/rule_types` - -=== {api-prereq-title} - -If you have `read` privileges for one or more {kib} features, the API response -contains information about the appropriate rule types. For example, there are -rule types associated with the *Management* > *Stack Rules* feature, -*Analytics* > *Discover* and *{ml-app}* features, *{observability}*, and -*Security* features. To get rule types associated with the -*{stack-monitor-app}* feature, use the `monitoring_user` built-in role. - -For more details, refer to <>. - -=== {api-description-title} - -Each rule type includes a list of authorized consumer features. For each feature, -users are authorized to perform either `read` or `all` operations on rules of -that type. This enables you to determine which rule types you can read, create, -or modify. If you want to create or edit a rule in {kib}, some rule types are -limited to specific features and apps. - -[[list-rule-types-api-params]] -=== {api-path-parms-title} - -`space_id`:: -(Optional, string) An identifier for the space. If `space_id` is not provided in -the URL, the default space is used. - -[[list-rule-types-response]] -=== {api-response-body-title} - -Each rule type has the following properties in the API response: - -`action_groups`:: -(array of objects) An explicit list of groups for which the rule type can -schedule actions, each with the action group's unique ID and human readable name. -Rule `actions` validation uses this configuration to ensure that groups are -valid. - -`action_variables`:: -(object) A list of action variables that the rule type makes available via -context and state in action parameter templates, and a short human readable -description. When you create a rule in {kib}, it uses this information to prompt -you for these variables in action parameter editors. - -`alerts`:: -(object) Details about alerts as data documents for this rule type, including any custom mappings. - -`authorized_consumers`:: -(object) The list of the plugins IDs that have access to the rule type. - -`category`:: -(string) The rule category, which is used by features such as category-specific maintenance windows. - -`default_action_group_id`:: -(string) The default ID for the rule type group. - -`does_set_recovery_context`:: -(boolean) Indicates whether the rule passes context variables to its recovery -action. - -`enabled_in_license`:: -(boolean) Indicates whether the rule type is enabled or disabled based on the -subscription. - -`has_alerts_mappings`:: -(boolean) Indicates whether the rule type has custom mappings for the alert data. - -// `has_fields_for_a_a_d`:: TBD - -`id`:: -(string) The unique identifier for the rule type. - -`is_exportable`:: -(boolean) Indicates whether the rule type is exportable in *{stack-manage-app}* -> *Saved Objects*. - -`minimum_license_required`:: -(string) The {subscriptions}[subscriptions] required to use the rule type. - -`name`:: -(string) The descriptive name of the rule type. - -`producer`:: -(string) An identifier for the application that produces this rule type. - -`recovery_action_group`:: -(object) An action group to use when an alert goes from an active state to an -inactive one. - -[[list-rule-types-api-codes]] -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -[[list-rule-types-api-example]] -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -GET api/alerting/rule_types --------------------------------------------------- -// KIBANA - -For example, if you have `read` privileges for the {observability} {logs-app}, -the API returns the following: - -[source,sh] --------------------------------------------------- -[ - { - "id":"logs.alert.document.count", - "name":"Log threshold", - "category": "observability", - "producer":"logs", - "alerts": { - "context": "observability.logs", - "mappings": { - "fieldMap": { - "kibana.alert.evaluation.threshold": { - "type": "scaled_float", - "scaling_factor": 100, - "required": false - }, - "kibana.alert.evaluation.value": { - "type": "scaled_float", - "scaling_factor": 100, - "required": false - }, - ... - } - }, - "useEcs": true, - "useLegacyAlerts": true - }, - "enabled_in_license":true, - "recovery_action_group":{ - "id":"recovered", - "name":"Recovered" - }, - "action_groups":[ - { - "id":"logs.threshold.fired", - "name":"Fired" - }, - { - "id":"recovered", - "name":"Recovered" - } - ], - "default_action_group_id":"logs.threshold.fired", - "minimum_license_required":"basic", - "is_exportable":true, - "rule_task_timeout":"5m", - "action_variables":{ - "context":[ - { - "name":"timestamp", - "description":"UTC timestamp of when the alert was triggered" - }, - { - "name":"matchingDocuments", - "description":"The number of log entries that matched the conditions provided" - }, - { - "name":"conditions", - "description":"The conditions that log entries needed to fulfill" - }, - ... - ], - "state":[], - "params":[] - }, - "authorized_consumers":{ - "logs":{"read":true,"all":false}, - "alerts":{"read":true,"all":false} - }, - "does_set_recovery_context":true, - "has_alerts_mappings": true, - "has_fields_for_a_a_d": true - }, - .... -] --------------------------------------------------- diff --git a/docs/api/alerting/mute_alert.asciidoc b/docs/api/alerting/mute_alert.asciidoc deleted file mode 100644 index 90d8e2e70ad303..00000000000000 --- a/docs/api/alerting/mute_alert.asciidoc +++ /dev/null @@ -1,55 +0,0 @@ -[[mute-alert-api]] -== Mute alert API -++++ -Mute alert -++++ - -Mute an alert. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[mute-alert-api-request]] -=== {api-request-title} - -`POST :/api/alerting/rule//alert//_mute` - -`POST :/s//api/alerting/rule//alert//_mute` - -=== {api-prereq-title} - -You must have `all` privileges for the appropriate {kib} features, depending on -the `consumer` and `rule_type_id` of the rule. For example, the -*Management* > *Stack Rules* feature, *Analytics* > *Discover* and *{ml-app}* -features, *{observability}*, and *Security* features. If the rule has `actions`, -you must also have `read` privileges for the *Management* > -*{connectors-feature}* feature. For more details, refer to -<>. - -[[mute-alert-api-path-params]] -=== {api-path-parms-title} - -`alert_id`:: - (Required, string) The ID of the alert that you want to mute. The `alert_id` is generated by the rule and might be any arbitrary string. - -`rule_id`:: - (Required, string) The ID of the rule whose alert you want to mute. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[mute-alert-api-response-codes]] -=== {api-response-codes-title} - -`204`:: - Indicates a successful call. - -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/alert/dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2/_mute --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/mute_all_alerts.asciidoc b/docs/api/alerting/mute_all_alerts.asciidoc deleted file mode 100644 index 1d9e388a4ca066..00000000000000 --- a/docs/api/alerting/mute_all_alerts.asciidoc +++ /dev/null @@ -1,57 +0,0 @@ -[[mute-all-alerts-api]] -== Mute all alerts API -++++ -Mute all alerts -++++ - -Mute all alerts. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[mute-all-alerts-api-request]] -=== {api-request-title} - -`POST :/api/alerting/rule//_mute_all` - -`POST :/s//api/alerting/rule//_mute_all` - -=== {api-prereq-title} - -You must have `all` privileges for the appropriate {kib} features, depending on -the `consumer` and `rule_type_id` of the rule. For example, the -*Management* > *Stack Rules* feature, *Analytics* > *Discover* and *{ml-app}* -features, *{observability}*, and *Security* features. If the rule has `actions`, -you must also have `read` privileges for the *Management* > -*{connectors-feature}* feature. For more details, refer to -<>. - -=== {api-description-title} - -This API snoozes the notifications for the rule indefinitely. The rule checks -continue to occur but alerts will not trigger any actions. - -[[mute-all-alerts-api-path-params]] -=== {api-path-parms-title} - -`id`:: - (Required, string) The ID of the rule whose alerts you want to mute. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[mute-all-alerts-api-response-codes]] -=== {api-response-codes-title} - -`204`:: - Indicates a successful call. - -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/_mute_all --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/unmute_alert.asciidoc b/docs/api/alerting/unmute_alert.asciidoc deleted file mode 100644 index 98471e2a8c3f44..00000000000000 --- a/docs/api/alerting/unmute_alert.asciidoc +++ /dev/null @@ -1,55 +0,0 @@ -[[unmute-alert-api]] -== Unmute alert API -++++ -Unmute alert -++++ - -Unmute an alert. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[unmute-alert-api-request]] -=== {api-request-title} - -`POST :/api/alerting/rule//alert//_unmute` - -`POST :/s//api/alerting/rule//alert//_unmute` - -=== {api-prereq-title} - -You must have `all` privileges for the appropriate {kib} features, depending on -the `consumer` and `rule_type_id` of the rule. For example, the -*Management* > *Stack Rules* feature, *Analytics* > *Discover* and *{ml-app}* -features, *{observability}*, and *Security* features. If the rule has `actions`, -you must also have `read` privileges for the *Management* > -*{connectors-feature}* feature. For more details, refer to -<>. - -[[unmute-alert-api-path-params]] -=== {api-path-parms-title} - -`alert_id`:: - (Required, string) The ID of the alert that you want to unmute. The `alert_id` is generated by the rule and might be any arbitrary string. - -`rule_id`:: - (Required, string) The ID of the rule whose alert you want to mute. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[unmute-alert-api-response-codes]] -=== {api-response-codes-title} - -`204`:: - Indicates a successful call. - -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/alert/dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2/_unmute --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/unmute_all_alerts.asciidoc b/docs/api/alerting/unmute_all_alerts.asciidoc deleted file mode 100644 index 99e3c7e2b4653e..00000000000000 --- a/docs/api/alerting/unmute_all_alerts.asciidoc +++ /dev/null @@ -1,57 +0,0 @@ -[[unmute-all-alerts-api]] -== Unmute all alerts API -++++ -Unmute all alerts -++++ - -Unmute all alerts. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[unmute-all-alerts-api-all-request]] -=== {api-request-title} - -`POST :/api/alerting/rule//_unmute_all` - -`POST :/s//api/alerting/rule//_unmute_all` - -=== {api-prereq-title} - -You must have `all` privileges for the appropriate {kib} features, depending on -the `consumer` and `rule_type_id` of the rule. For example, the -*Management* > *Stack Rules* feature, *Analytics* > *Discover* and *{ml-app}* -features, *{observability}*, and *Security* features. If the rule has `actions`, -you must also have `read` privileges for the *Management* > -*{connectors-feature}* feature. For more details, refer to -<>. - -=== {api-description-title} - -If the rule has its notifications snoozed indefinitely, this API cancels the -snooze. - -[[unmute-all-alerts-api-path-params]] -=== {api-path-parms-title} - -`id`:: - (Required, string) The ID of the rule whose alerts you want to unmute. - -`space_id`:: - (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. - -[[unmute-all-alerts-api-response-codes]] -=== {api-response-codes-title} - -`204`:: - Indicates a successful call. - -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/_unmute_all --------------------------------------------------- -// KIBANA diff --git a/docs/api/alerting/update_rule.asciidoc b/docs/api/alerting/update_rule.asciidoc deleted file mode 100644 index d78a2981ce9de5..00000000000000 --- a/docs/api/alerting/update_rule.asciidoc +++ /dev/null @@ -1,196 +0,0 @@ -[[update-rule-api]] -== Update rule API -++++ -Update rule -++++ - -Update the attributes for an existing rule. - -[NOTE] -==== -For the latest details, refer to {api-kibana}/group/endpoint-alerting[alerting APIs]. -==== - -[[update-rule-api-request]] -=== {api-request-title} - -`PUT :/api/alerting/rule/` - -`PUT :/s//api/alerting/rule/` - -=== {api-prereq-title} - -You must have `all` privileges for the appropriate {kib} features, depending on -the `consumer` and `rule_type_id` of the rule you're updating. For example, the -*Management* > *Stack Rules* feature, *Analytics* > *Discover* and *{ml-app}* -features, *{observability}*, or *Security* features. If the rule has -`actions`, you must also have `read` privileges for the *Management* > -*{connectors-feature}* feature. For more details, refer to -<>. - -[[update-rule-api-path-params]] -=== {api-path-parms-title} - -`id`:: -(Required, string) The ID of the rule that you want to update. - -`space_id`:: -(Optional, string) An identifier for the space. If `space_id` is not provided in -the URL, the default space is used. - -[role="child_attributes"] -[[update-rule-api-request-body]] -=== {api-request-body-title} - -`actions`:: -(Optional, object array) An array of action objects. The default value is an -empty array (`[]`). -+ -.Properties of the action objects: -[%collapsible%open] -===== -`group`::: -(Required, string) Grouping actions is recommended for escalations for different -types of alerts. If you don't need this, set the value to `default`. - -`id`::: -(Required, string) The identifier of the action. - -`params`::: -(Required, object) The map to the `params` that the -<> will receive. The `params` are handled as -Mustache templates and passed a default set of context. -===== - -`name`:: -(Required, string) A name to reference and search. - -`notify_when`:: -(Required, string) The condition for throttling the notification: -`onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`. - -`params`:: -(Required, object) The parameters to pass to the rule type executor `params` -value. This will also validate against the rule type params validator, if defined. - -`schedule`:: -(Required, object) When to run this rule. Use one of the available schedule formats. -+ -.Schedule formats -[%collapsible%open] -===== -A schedule uses a `key: value` format. {kib} currently supports the -_interval format_, which specifies the interval in seconds, minutes, hours, or -days at which to run the rule. For example: `{ "interval": "10s" }`, -`{ "interval": "5m" }`, `{ "interval": "1h" }`, or `{ "interval": "1d" }`. - -===== - -`tags`:: -(Optional, string array) A list of keywords to reference and search. The default -value is an empty array (`[]`). - -`throttle`:: -(Optional, string) How often this rule should fire the same actions. This will -prevent the rule from sending out the same notification over and over. For -example, if a rule with a `schedule` of 1 minute stays in a triggered state for -90 minutes, setting a `throttle` of `10m` or `1h` will prevent it from sending -90 notifications during this period. The default value is `null`. - -[[update-rule-api-response-codes]] -=== {api-response-codes-title} - -`200`:: -Indicates a successful call. - -[[update-rule-api-example]] -=== {api-examples-title} - -Update an index threshold rule with ID `ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74`: - -[source,sh] --------------------------------------------------- -PUT api/alerting/rule/ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 -{ - "notify_when": "onActionGroupChange", - "params": { - "index":[".test-index"], - "timeField":"@timestamp", - "groupBy":"top", - "aggType":"avg", - "timeWindowSize":5, - "timeWindowUnit":"m", - "thresholdComparator":">", - "threshold":[1000], - "aggField":"sheet.version", - "termField":"name.keyword", - "termSize":6 - }, - "schedule": { - "interval": "1m" - }, - "actions": [], - "tags": [], - "name": "new name", - "throttle": null -} --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,sh] --------------------------------------------------- -{ - "id": "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74", - "consumer": "alerts", - "tags": [], - "name": "new name", - "enabled": true, - "throttle": null, - "schedule": { - "interval": "1m" - }, - "params": { - "index": [".updated-index"], - "timeField": "@timestamp", - "groupBy": "top", - "aggType": "avg", - "timeWindowSize": 5, - "timeWindowUnit": "m", - "thresholdComparator": ">", - "threshold": [1000], - "aggField": "sheet.version", - "termField": "name.keyword", - "termSize": 6 - }, - "api_key_owner": "elastic", - "created_by": "elastic", - "updated_by": "elastic", - "rule_type_id": ".index-threshold", - "scheduled_task_id": "4c5eda00-e74f-11ec-b72f-5b18752ff9ea", - "created_at": "2022-12-12T22:43:20.578Z", - "updated_at": "2022-12-12T22:44:21.783Z", - "notify_when": "onActionGroupChange", - "mute_all": false, - "muted_alert_ids": [], - "execution_status": { - "status": "ok", - "last_execution_date": "2022-12-12T22:43:21.723Z", - "last_duration": 125 - }, - "actions":[], - "last_run":{ - "alerts_count": { - "new": 0, - "ignored": 0, - "recovered": 0, - "active": 0 - }, - "outcome_msg" :null, - "warning": null, - "outcome": "succeeded" - }, - "next_run": "2022-12-12T22:44:21.653Z" -} --------------------------------------------------- diff --git a/docs/api/cases.asciidoc b/docs/api/cases.asciidoc index 2df6ae994bc667..1798a44ac825eb 100644 --- a/docs/api/cases.asciidoc +++ b/docs/api/cases.asciidoc @@ -1,34 +1,4 @@ [[cases-api]] == Cases APIs -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[case APIs]. - -//ADD -include::cases/cases-api-add-comment.asciidoc[leveloffset=+1] -//CREATE -include::cases/cases-api-create.asciidoc[leveloffset=+1] -//DELETE -include::cases/cases-api-delete-cases.asciidoc[leveloffset=+1] -include::cases/cases-api-delete-comments.asciidoc[leveloffset=+1] -//FIND -include::cases/cases-api-find-case-activity.asciidoc[leveloffset=+1] -include::cases/cases-api-find-cases.asciidoc[leveloffset=+1] -include::cases/cases-api-find-connectors.asciidoc[leveloffset=+1] -//GET -include::cases/cases-api-get-alerts.asciidoc[leveloffset=+1] -include::cases/cases-api-get-case-activity.asciidoc[leveloffset=+1] -include::cases/cases-api-get-case.asciidoc[leveloffset=+1] -include::cases/cases-api-get-status.asciidoc[leveloffset=+1] -include::cases/cases-api-get-cases-by-alert.asciidoc[leveloffset=+1] -include::cases/cases-api-get-comments.asciidoc[leveloffset=+1] -include::cases/cases-api-get-configuration.asciidoc[leveloffset=+1] -include::cases/cases-api-get-reporters.asciidoc[leveloffset=+1] -include::cases/cases-api-get-tags.asciidoc[leveloffset=+1] -//PUSH -include::cases/cases-api-push.asciidoc[leveloffset=+1] -//SET -include::cases/cases-api-set-configuration.asciidoc[leveloffset=+1] -//UPDATE -include::cases/cases-api-update.asciidoc[leveloffset=+1] -include::cases/cases-api-update-comment.asciidoc[leveloffset=+1] -include::cases/cases-api-update-configuration.asciidoc[leveloffset=+1] +For the latest details, refer to {api-kibana}/group/endpoint-cases[case APIs]. diff --git a/docs/api/cases/cases-api-add-comment.asciidoc b/docs/api/cases/cases-api-add-comment.asciidoc deleted file mode 100644 index 9fcb79ea92cd1e..00000000000000 --- a/docs/api/cases/cases-api-add-comment.asciidoc +++ /dev/null @@ -1,178 +0,0 @@ -[[cases-api-add-comment]] -== Add comment to case API -++++ -Add comment -++++ - -Adds a comment or alert to a case. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`POST :/api/cases//comments` - -`POST :/s//api/cases//comments` - -=== {api-prereq-title} - -You must have `all` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the case you're updating. - -=== {api-description-title} - -NOTE: Each case can have a maximum of 1,000 alerts. - -=== {api-path-parms-title} - -``:: -(Required,string) The identifier for the case. To retrieve case IDs, use -<>. - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -[role="child_attributes"] -=== {api-request-body-title} - -`alertId`:: -(Required*, string or array of strings) The alert identifiers. It is required -only when `type` is `alert`. You can use an array of strings to add multiple -alerts to a case, provided that they all relate to the same rule; `index` must -also be an array with the same length or number of elements. Adding multiple -alerts in this manner is recommended rather than calling the API multiple times. -preview:[] - -`comment`:: -(Required*, string) The new comment. It is required only when `type` is `user`. - -`index`:: -(Required*, string or array of strings) The alert indices. It is required only -when `type` is `alert`. If you are adding multiple alerts to a case, use an array -of strings; the position of each index name in the array must match the position -of the corresponding alert identifier in the `alertId` array. preview:[] - -`owner`:: -(Required, string) The application that owns the case. Valid values are: -`cases`, `observability`, or `securitySolution`. - -`rule`:: -(Required*, object) The rule that is associated with the alerts. It is required -only when `type` is `alert`. preview:[] -+ -.Properties of `rule` -[%collapsible%open] -==== -`id`:: -(Required, string) The rule identifier. preview:[] - -`name`:: -(Required, string) The rule name. preview:[] - -==== - -`type`:: -(Required, string) The comment type, which must be `user` or `alert`. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Add a comment to case ID `293f1bc0-74f6-11ea-b83a-553aecdb28b6`: - -[source,sh] --------------------------------------------------- -POST api/cases/293f1bc0-74f6-11ea-b83a-553aecdb28b6/comments -{ - "type": "user", - "comment": "A new comment.", - "owner": "cases" -} --------------------------------------------------- -// KIBANA - -The API returns details about the case and its comments. For example: - -[source,json] --------------------------------------------------- -{ - "comments":[ - { - "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", - "version": "WzIwNDMxLDFd", - "type": "user", - "owner": "cases", - "comment": "A new comment.", - "created_at": "2022-03-24T00:49:47.716Z", - "created_by": { - "email": null, - "full_name": null, - "username": "elastic" - }, - "pushed_at": null, - "pushed_by": null, - "updated_at": null, - "updated_by": null - } - ], - "totalAlerts": 0, - "id": "293f1bc0-74f6-11ea-b83a-553aecdb28b6", - "version": "WzIzMzgsMV0=", - "totalComment": 1, - "title": "Case title 1", - "tags": ["tag 1"], - "description": "A case description.", - "settings": { - "syncAlerts": false - }, - "owner": "cases", - "duration": null, - "severity": "low", - "closed_at": null, - "closed_by": null, - "created_at": "2022-03-24T00:37:03.906Z", - "created_by": { - "email": null, - "full_name": null, - "username": "elastic" - }, - "status": "open", - "updated_at": "2022-03-24T00:49:47.716Z", - "updated_by": { - "email": null, - "full_name": null, - "username": "elastic" - }, - "connector": { - "id": "none", - "name": "none", - "type": ".none", - "fields": null - }, - "external_service": null -} --------------------------------------------------- - -Add an alert to the case: - -[source,sh] --------------------------------------------------- -POST api/cases/293f1bc0-74f6-11ea-b83a-553aecdb28b6/comments -{ - "alertId": "6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42", - "index": ".internal.alerts-security.alerts-default-000001", - "type": "alert", - "owner": "cases", - "rule": { - "id":"94d80550-aaf4-11ec-985f-97e55adae8b9", - "name":"security_rule" - } -} --------------------------------------------------- -// KIBANA diff --git a/docs/api/cases/cases-api-create.asciidoc b/docs/api/cases/cases-api-create.asciidoc deleted file mode 100644 index 962b23b35badf7..00000000000000 --- a/docs/api/cases/cases-api-create.asciidoc +++ /dev/null @@ -1,255 +0,0 @@ -[[cases-api-create]] -== Create case API -++++ -Create case -++++ - -Creates a case. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`POST :/api/cases` - -`POST :/s//api/cases` - -=== {api-prereq-title} - -You must have `all` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the case you're creating. - -=== {api-path-parms-title} - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -[role="child_attributes"] -=== {api-request-body-title} - -`assignees`:: -(Optional, array of objects) Array containing users that are assigned to the case. -+ -.Properties of assignee objects -[%collapsible%open] -===== -`uid`:: -(Required, string) A unique identifier for the user profile. These identifiers -can be found by using the -{ref}/security-api-suggest-user-profile.html[suggest user profile API]. -===== - -`connector`:: -(Required, object) An object that contains the connector configuration. -+ -.Properties of `connector` -[%collapsible%open] -==== -`fields`:: -(Required, object) An object containing the connector fields. To create a case -without a connector, specify `null`. If you want to omit any individual field, -specify `null` as its value. -+ -.Properties of `fields` -[%collapsible%open] -===== -For {ibm-r} connectors, specify: - -`issueTypes`::: -(Required, array of strings) The type of the incident. - -`severityCode`::: -(Required, string) The severity code of the incident. - -For {jira} connectors, specify: - -`issueType`::: -(Required, string) The type of the issue. - -`parent`::: -(Required, string) The key of the parent issue, when the issue type is `Sub-task`. - -`priority`::: -(Required, string) The priority of the issue. - -For {sn-itsm} connectors, specify: - -`category`::: -(Required, string) The category of the incident. - -`impact`::: -(Required, string) The effect an incident had on business. - -`severity`::: -(Required, string) The severity of the incident. - -`subcategory`::: -(Required, string) The subcategory of the incident. - -`urgency`::: -(Required, string) The extent to which the incident resolution can be delayed. - -For {sn-sir} connectors, specify: - -`category`::: -(Required, string) The category of the incident. - -`destIp`::: -(Required, string) A comma separated list of destination IPs. - -`malwareHash`::: -(Required, string) A comma separated list of malware hashes. - -`malwareUrl`::: -(Required, string) A comma separated list of malware URLs. - -`priority`::: -(Required, string) The priority of the incident. - -`sourceIp`::: -(Required, string) A comma separated list of source IPs. - -`subcategory`::: -(Required, string) The subcategory of the incident. - -For {swimlane} connectors, specify: - -`caseId`::: -(Required, string) The case ID. - -For {webhook-cm} connectors, specify `null`. -===== - -`id`:: -(Required, string) The identifier for the connector. To create a case without a -connector, use `none`. To retrieve connector IDs, use -<>. - -`name`:: -(Required, string) The name of the connector. To create a case without a -connector, use `none`. - -`type`:: -(Required, string) The type of the connector. Valid values are: `.cases-webhook`, -`.jira`, `.none`, `.resilient`,`.servicenow`, `.servicenow-sir`, and `.swimlane`. -To create a case without a connector, use `.none`. - -==== - -`description`:: -(Required, string) The description for the case. - -`owner`:: -(Required, string) The application that owns the case. Valid values are: -`cases`, `observability`, or `securitySolution`. This value affects -whether the case is visible in the {stack-manage-app}, {observability}, or -{security-app}. - -`settings`:: -(Required, object) -An object that contains the case settings. -+ -.Properties of `settings` -[%collapsible%open] -==== -`syncAlerts`:: -(Required, boolean) Turns alert syncing on or off. -==== - -`severity`:: -(Optional,string) The severity of the case. Valid values are: `critical`, `high`, -`low`, and `medium`. - -`tags`:: -(Required, string array) The words and phrases that help -categorize cases. It can be an empty array. - -`title`:: -(Required, string) A title for the case. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -POST api/cases -{ - "description": "A case description.", - "title": "Case title 1", - "tags": [ "tag 1" ], - "connector": { - "id": "131d4448-abe0-4789-939d-8ef60680b498", - "name": "My connector", - "type": ".jira", - "fields": { - "issueType": "10006", - "priority": "High", - "parent": null - } - }, - "settings": { - "syncAlerts": true - }, - "owner": "cases" -} --------------------------------------------------- -// KIBANA - -The API returns a JSON object that includes the user who created the case and -the case identifier, version, and creation time. For example: - -[source,json] --------------------------------------------------- -{ - "id": "66b9aa00-94fa-11ea-9f74-e7e108796192", <1> - "version": "WzUzMiwxXQ==", - "comments": [], - "totalComment": 0, - "totalAlerts": 0, - "title": "Case title 1", - "tags": [ "tag 1" ], - "assignees": [], - "settings": { - "syncAlerts": true - }, - "owner": "cases", - "description": "A case description.", - "duration": null, - "severity": "low", - "closed_at": null, - "closed_by": null, - "created_at": "2022-05-13T09:16:17.416Z", - "created_by": { - "email": null, - "full_name": null, - "username": "elastic" - }, - "status": "open", - "updated_at": null, - "updated_by": null, - "connector": { - "id": "131d4448-abe0-4789-939d-8ef60680b498", <2> - "name": "My connector", - "type": ".jira", - "fields": { - "issueType": "10006", - "parent": null, - "priority": "High" - } - }, - "external_service": null <3> -} --------------------------------------------------- - -<1> The case identifier is also its saved object ID (`savedObjectId`), which is -used when pushing cases to external systems. -<2> The default connector used to push cases to external services. -<3> The `external_service` object stores information about the incident after it -is pushed to an external incident management system. \ No newline at end of file diff --git a/docs/api/cases/cases-api-delete-cases.asciidoc b/docs/api/cases/cases-api-delete-cases.asciidoc deleted file mode 100644 index d4c26912f1ffa1..00000000000000 --- a/docs/api/cases/cases-api-delete-cases.asciidoc +++ /dev/null @@ -1,54 +0,0 @@ -[[cases-api-delete-cases]] -== Delete cases API -++++ -Delete cases -++++ - -Deletes one or more cases. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`DELETE :/api/cases?ids=["",""]` - -`DELETE :/s//api/cases?ids=["",""]` - -=== {api-prereq-title} - -You must have `read` or `all` privileges and the `delete` sub-feature privilege -for the *Cases* feature in the *Management*, *{observability}*, or *Security* -section of the <>, depending -on the `owner` of the cases you're deleting. - -=== {api-path-parms-title} - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-query-parms-title} - -`ids`:: -(Required, string) The cases that you want to remove. To retrieve case IDs, use -<>. -+ -NOTE: All non-ASCII characters must be URL encoded. - -=== {api-response-codes-title} - -`204`:: - Indicates a successful call. - -=== {api-examples-title} - -Delete cases with these IDs: - -* `2e3a54f0-6754-11ea-a1c2-e3a8bc9f7aca` -* `40b9a450-66a0-11ea-be1b-2bd3fef48984` - -[source,console] --------------------------------------------------- -DELETE api/cases?ids=%5B%222e3a54f0-6754-11ea-a1c2-e3a8bc9f7aca%22%2C%2240b9a450-66a0-11ea-be1b-2bd3fef48984%22%5D --------------------------------------------------- -// KIBANA diff --git a/docs/api/cases/cases-api-delete-comments.asciidoc b/docs/api/cases/cases-api-delete-comments.asciidoc deleted file mode 100644 index b704f328d058bc..00000000000000 --- a/docs/api/cases/cases-api-delete-comments.asciidoc +++ /dev/null @@ -1,65 +0,0 @@ -[[cases-api-delete-comments]] -== Delete comments from case API -++++ -Delete comments -++++ - -Deletes one or all comments and alerts from a case. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`DELETE :/api/cases//comments` - -`DELETE :/api/cases//comments/` - -`DELETE :/s//api/cases//comments` - -`DELETE :/s//api/cases//comments/` - -=== {api-prereq-title} - -You must have `read` or `all` privileges and the `delete` sub-feature privilege -for the *Cases* feature in the *Management*, *{observability}*, or *Security* -section of the <>, depending -on the `owner` of the cases you're updating. - -=== {api-path-parms-title} - -``:: -(Required, string) The identifier for the case. To retrieve case IDs, use -<>. - -``:: -(Optional, string) The identifier for the comment. To retrieve comment IDs, use -<> or <>. If it is not specified, all -comments are deleted. - -:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-response-codes-title} - -`204`:: - Indicates a successful call. - -=== {api-examples-title} - -Delete all comments from case ID `9c235210-6834-11ea-a78c-6ffb38a34414`: - -[source,console] --------------------------------------------------- -DELETE api/cases/a18b38a0-71b0-11ea-a0b2-c51ea50a58e2/comments --------------------------------------------------- -// KIBANA - -Delete comment ID `71ec1870-725b-11ea-a0b2-c51ea50a58e2` from case ID -`a18b38a0-71b0-11ea-a0b2-c51ea50a58e2`: - -[source,sh] --------------------------------------------------- -DELETE api/cases/a18b38a0-71b0-11ea-a0b2-c51ea50a58e2/comments/71ec1870-725b-11ea-a0b2-c51ea50a58e2 --------------------------------------------------- -// KIBANA diff --git a/docs/api/cases/cases-api-find-case-activity.asciidoc b/docs/api/cases/cases-api-find-case-activity.asciidoc deleted file mode 100644 index 9f7367fd7577be..00000000000000 --- a/docs/api/cases/cases-api-find-case-activity.asciidoc +++ /dev/null @@ -1,16 +0,0 @@ -[[cases-api-find-case-activity]] -== Find case activity API -++++ -Find case activity -++++ - -Finds user activity for a case. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`GET :/api/cases//user_actions/_find` - -`GET :/s//api/cases//user_actions/_find` - diff --git a/docs/api/cases/cases-api-find-cases.asciidoc b/docs/api/cases/cases-api-find-cases.asciidoc deleted file mode 100644 index 636a011457a63c..00000000000000 --- a/docs/api/cases/cases-api-find-cases.asciidoc +++ /dev/null @@ -1,167 +0,0 @@ -[[cases-api-find-cases]] -== Find cases API -++++ -Find cases -++++ - -Retrieves a paginated subset of cases. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`GET :/api/cases/_find` - -`GET :/s//api/cases/_find` - -=== {api-prereq-title} - -You must have `read` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the cases you're seeking. - -=== {api-path-parms-title} - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-query-parms-title} - -`assignees`:: -(Optional, string or array of strings) Filters the returned cases by assignees. -Valid values are `none` or unique identifiers for the user profiles. These -identifiers can be found by using the -{ref}/security-api-suggest-user-profile.html[suggest user profile API]. - -`defaultSearchOperator`:: -(Optional, string) The default operator to use for the `simple_query_string`. -Defaults to `OR`. - -`from`:: -(Optional, string) Returns only cases that were created after a specific date. The date must be specified as a <> data range or date match expression. preview:[] - -`owner`:: -(Optional, string or array of strings) A filter to limit the retrieved cases to -a specific set of applications. Valid values are: `cases`, `observability`, -and `securitySolution`. If this parameter is omitted, the response contains all -cases that the user has access to read. - -`page`:: -(Optional, integer) The page number to return. Defaults to `1`. - -`perPage`:: -(Optional, integer) The number of rules to return per page. Defaults to `20`. - -`reporters`:: -(Optional, string or array of strings) Filters the returned cases by the -reporter's `username`. - -`search`:: -(Optional, string) An {es} -{ref}/query-dsl-simple-query-string-query.html[simple_query_string] query that -filters the objects in the response. - -`searchFields`:: -(Optional, string or array of strings) The fields to perform the -`simple_query_string` parsed query against. - -`severity`:: -(Optional,string) The severity of the case. Valid values are: `critical`, `high`, -`low`, and `medium`. - -`sortField`:: -(Optional, string) Determines which field is used to sort the results, -`createdAt` or `updatedAt`. Defaults to `createdAt`. -+ -NOTE: Even though the JSON case object uses `created_at` and `updated_at` -fields, you must use `createdAt` and `updatedAt` fields in the URL -query. - -`sortOrder`:: -(Optional, string) Determines the sort order, which can be `desc` or `asc`. -Defaults to `desc`. - -`status`:: -(Optional, string) Filters the returned cases by state, which can be `open`, -`in-progress`, or `closed`. - -`tags`:: -(Optional, string or array of strings) Filters the returned cases by tags. - -`to`:: -(Optional, string) Returns only cases that were created before a specific date. The date must be specified as a <> data range or date match expression. preview:[] - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Retrieve the first five cases with the `tag-1` tag, in ascending order by -last update time: - -[source,sh] --------------------------------------------------- -GET api/cases/_find?page=1&perPage=5&sortField=updatedAt&sortOrder=asc&tags=tag-1 --------------------------------------------------- -// KIBANA - -The API returns a JSON object listing the retrieved cases. For example: - -[source,json] --------------------------------------------------- -{ - "page": 1, - "per_page": 5, - "total": 1, - "cases": [ - { - "id": "abed3a70-71bd-11ea-a0b2-c51ea50a58e2", - "version": "WzExMCwxXQ==", - "comments": [], - "totalComment": 1, - "totalAlerts": 0, - "title": "Case title", - "tags": [ "tag-1" ], - "description": "Case description", - "settings": { "syncAlerts": true }, - "owner": "securitySolution", - "duration": null, <1> - "severity": "low", - "closed_at": null, - "closed_by": null, - "created_at": "2022-05-12T00:16:36.371Z", - "created_by": { - "email": "jdoe@email.com", - "full_name": "Jane Doe", - "username": "jdoe" - }, - "status": "open", - "updated_at": "2022-05-12T00:27:58.162Z", - "updated_by": { - "email": "jsmith@email.com", - "full_name": "Joe Smith", - "username": "jsmith" - }, - "assignees": [], - "connector": { - "id": "none", - "name": "none", - "type": ".none", - "fields": null - }, - "external_service": null - } - ], - "count_open_cases": 1, - "count_in_progress_cases":0, - "count_closed_cases": 0 -} --------------------------------------------------- -<1> Duration represents the elapsed time from the creation of the case to its -closure (in seconds). If the case has not been closed, the duration is set to -`null`. If the case was closed after less than half a second, the duration is -rounded down to zero. \ No newline at end of file diff --git a/docs/api/cases/cases-api-find-connectors.asciidoc b/docs/api/cases/cases-api-find-connectors.asciidoc deleted file mode 100644 index 8bb4fe80686c70..00000000000000 --- a/docs/api/cases/cases-api-find-connectors.asciidoc +++ /dev/null @@ -1,63 +0,0 @@ -[[cases-api-find-connectors]] -== Find connectors API -++++ -Find connectors -++++ - -Retrieves information about <>. - -In particular, only the connectors that are supported for use in cases are -returned. Refer to the list of supported external incident management systems in -<>. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`GET :/api/cases/configure/connectors/_find` - -`GET :/s//api/cases/configure/connectors/_find` - -=== {api-prereq-title} - -You must have `read` privileges for the *{connectors-feature}* feature in the -*Management* section of the -<>. - -=== {api-path-parms-title} - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -GET api/cases/configure/connectors/_find --------------------------------------------------- -// KIBANA - -The API returns a JSON object describing the connectors and their settings: - -[source,json] --------------------------------------------------- -[{ - "id":"61787f53-4eee-4741-8df6-8fe84fa616f7", - "actionTypeId": ".jira", - "name":"my-Jira", - "isMissingSecrets":false, - "config": { - "apiUrl":"https://elastic.atlassian.net/", - "projectKey":"ES" - }, - "isPreconfigured":false, - "isDeprecated": false, - "referencedByCount":0 -}] --------------------------------------------------- \ No newline at end of file diff --git a/docs/api/cases/cases-api-get-alerts.asciidoc b/docs/api/cases/cases-api-get-alerts.asciidoc deleted file mode 100644 index 5b07d7c7efd964..00000000000000 --- a/docs/api/cases/cases-api-get-alerts.asciidoc +++ /dev/null @@ -1,62 +0,0 @@ -[[cases-api-get-alerts]] -== Get alerts attached to case API -++++ -Get alerts -++++ - -preview::[] - -Gets all alerts attached to a case. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`GET :/api/cases//alerts` - -`GET :/s//api/cases//alerts` - -=== {api-prereq-title} - -You must have `read` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the cases you're seeking. - -=== {api-path-parms-title} - -``:: -(Required, string) The identifier for the case. To retrieve case IDs, use -<>. - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Return all alerts attached to case `a8b26350-0c55-11ed-918a-2d2edf3e58bc`: - -[source,sh] --------------------------------------------------- -GET api/cases/a8b26350-0c55-11ed-918a-2d2edf3e58bc/alerts --------------------------------------------------- -// KIBANA - -The API returns a JSON array listing the alerts. For example: - -[source,json] --------------------------------------------------- -[ - { - "id": "f6a7d0c3-d52d-432c-b2e6-447cd7fce04d", - "index": ".alerts-observability.logs.alerts-default", - "attached_at": "2022-07-25T20:09:40.963Z" - } -] --------------------------------------------------- diff --git a/docs/api/cases/cases-api-get-case-activity.asciidoc b/docs/api/cases/cases-api-get-case-activity.asciidoc deleted file mode 100644 index d0fee376c0f653..00000000000000 --- a/docs/api/cases/cases-api-get-case-activity.asciidoc +++ /dev/null @@ -1,107 +0,0 @@ -[[cases-api-get-case-activity]] -== Get case activity API -++++ -Get case activity -++++ - -Returns all user activity for a case. - -deprecated::[8.1.0,Use <> instead.] - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`GET :/api/cases//user_actions` - -`GET :/s//api/cases//user_actions` - -=== {api-prereq-title} - -You must have `read` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the case you're seeking. - -=== {api-path-parms-title} - -``:: -(Required, string) An identifier for the case to retrieve. Use -<> to retrieve case IDs. - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Gets all activity for case ID `22df07d0-03b1-11ed-920c-974bfa104448`: - -[source,sh] --------------------------------------------------- -GET api/cases/22df07d0-03b1-11ed-920c-974bfa104448/user_actions --------------------------------------------------- -// KIBANA - -The API returns a JSON object with all the activity for the case. For example: - -[source,json] --------------------------------------------------- -[ - { - "created_at":"2022-07-14T20:11:24.947Z", - "created_by":{ - "username":"elastic", - "email":null, - "full_name":null - }, - "owner":"cases", - "action":"create", - "payload":{ - "description":"A case description", - "title":"Case title 1", - "tags":["tag 1"], - "connector":{ - "name":"none", - "type":".none", - "fields":null, - "id":"none" - }, - "settings":{"syncAlerts":true}, - "owner":"cases", - "severity":"low", - "status":"open" - }, - "type":"create_case", - "action_id":"22fd3e30-03b1-11ed-920c-974bfa104448", - "case_id":"22df07d0-03b1-11ed-920c-974bfa104448", - "comment_id":null - }, - { - "created_at":"2022-07-14T20:12:53.354Z", - "created_by":{ - "username":"elastic", - "email":null, - "full_name":null - }, - "owner":"cases", - "action":"create", - "payload":{ - "comment":{ - "type":"user", - "owner":"cases", - "comment":"A new comment" - } - }, - "type":"comment", - "action_id":"57af14a0-03b1-11ed-920c-974bfa104448", - "case_id":"22df07d0-03b1-11ed-920c-974bfa104448", - "comment_id":"578608d0-03b1-11ed-920c-974bfa104448" - } -] --------------------------------------------------- \ No newline at end of file diff --git a/docs/api/cases/cases-api-get-case.asciidoc b/docs/api/cases/cases-api-get-case.asciidoc deleted file mode 100644 index f0a7d3aba3404e..00000000000000 --- a/docs/api/cases/cases-api-get-case.asciidoc +++ /dev/null @@ -1,99 +0,0 @@ -[[cases-api-get-case]] -== Get case API -++++ -Get case -++++ - -Returns information about a case. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`GET :/api/cases/` - -`GET :/s//api/cases/` - -=== {api-prereq-title} - -You must have `read` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the case you're seeking. - -=== {api-path-parms-title} - -``:: -(Required, string) An identifier for the case to retrieve. Use -<> to retrieve case IDs. - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-query-parms-title} - -`includeComments`:: -(Optional, boolean) Determines whether case comments are returned. Defaults to -`true`. deprecated:[8.1.0, "The `includeComments` query parameter is deprecated and will be removed in a future release."] - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Returns case ID `31cdada0-02c1-11ed-85f2-4f7c222ca2fa`: - -[source,sh] --------------------------------------------------- -GET api/cases/31cdada0-02c1-11ed-85f2-4f7c222ca2fa --------------------------------------------------- -// KIBANA - -The API returns a JSON object with the retrieved case. For example: - -[source,json] --------------------------------------------------- -{ - "id":"31cdada0-02c1-11ed-85f2-4f7c222ca2fa", - "version":"WzM2LDFd", - "comments":[{ - "id":"2134c1d0-02c2-11ed-85f2-4f7c222ca2fa", - "version":"WzM3LDFd", - "type":"user", - "owner":"cases", - "comment":"A new comment", - "created_at":"2022-07-13T15:40:32.335Z", - "created_by":{"email":null,"full_name":null,"username":"elastic"}, - "pushed_at":null, - "pushed_by":null, - "updated_at":null, - "updated_by":null - }], - "totalComment":1, - "totalAlerts":0, - "title":"Case title 1", - "tags":["tag 1"], - "settings":{"syncAlerts":true}, - "owner":"cases", - "description":"A case description", - "duration":null, <1> - "severity":"low", - "closed_at":null, - "closed_by":null, - "created_at":"2022-07-13T15:33:50.604Z", - "created_by":{"username":"elastic","email":null,"full_name":null}, - "status":"open", - "updated_at":"2022-07-13T15:40:32.335Z", - "updated_by":{"full_name":null,"email":null,"username":"elastic"}, - "assignees":[{"uid":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"}], - "connector":{"id":"none","name":"none","type":".none","fields":null}, - "external_service":null -} --------------------------------------------------- -<1> Duration represents the elapsed time from the creation of the case to its -closure (in seconds). If the case has not been closed, the duration is set to -`null`. If the case was closed after less than half a second, the duration is -rounded down to zero. \ No newline at end of file diff --git a/docs/api/cases/cases-api-get-cases-by-alert.asciidoc b/docs/api/cases/cases-api-get-cases-by-alert.asciidoc deleted file mode 100644 index ff010c28df994f..00000000000000 --- a/docs/api/cases/cases-api-get-cases-by-alert.asciidoc +++ /dev/null @@ -1,71 +0,0 @@ -[[cases-api-get-cases-by-alert]] -== Get cases by alert API -++++ -Get cases by alert -++++ - -preview::[] - -Returns the cases associated with a specific alert. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - - -=== {api-request-title} - -`GET :/api/cases/alerts/` - -`GET :/s//api/cases/alerts/` - -=== {api-prereq-title} - -You must have `read` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the cases you're seeking. - -=== {api-path-parms-title} - -``:: -(Required, string) The alert identifier. - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-query-parms-title} - -`owner`:: -(Optional, string or array of strings) A filter to limit the retrieved cases to -a specific set of applications. Valid values are: `cases`, `observability`, -and `securitySolution`. If this parameter is omitted, the response contains all -cases that the user has access to read. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Return cases associated with the alert ID -`09f0c261e39e36351d75995b78bb83673774d1bc2cca9df2d15f0e5c0a99a540`: - -[source,sh] --------------------------------------------------- -GET api/cases/alerts/09f0c261e39e36351d75995b78bb83673774d1bc2cca9df2d15f0e5c0a99a540 --------------------------------------------------- -// KIBANA - -The API returns a JSON array containing the identifier and title of the cases. -For example: - -[source,json] --------------------------------------------------- -[ - { - "id": "06116b80-e1c3-11ec-be9b-9b1838238ee6", - "title":"security_case" - } -] --------------------------------------------------- \ No newline at end of file diff --git a/docs/api/cases/cases-api-get-comments.asciidoc b/docs/api/cases/cases-api-get-comments.asciidoc deleted file mode 100644 index 1eb3d0087766fd..00000000000000 --- a/docs/api/cases/cases-api-get-comments.asciidoc +++ /dev/null @@ -1,82 +0,0 @@ -[[cases-api-get-comments]] -== Get comments API -++++ -Get comments -++++ - -Gets a comment or all comments for a case. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`GET :/api/cases//comments/` - -`GET :/s//api/cases//comments/` - -`GET :/api/cases//comments` deprecated:[8.1.0] - -`GET :/s//api/cases//comments` deprecated:[8.1.0] - -=== {api-prereq-title} - -You must have `read` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the cases with the comments you're seeking. - -=== {api-path-parms-title} - -``:: -(Required, string) The identifier for the case. To retrieve case IDs, use -<>. - -``:: -(Optional, string) The identifier for the comment. To retrieve comment IDs, use -<>. -+ -If it is not specified, all comments are retrieved. -deprecated:[8.1.0,The comment identifier will no longer be optional.] - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Retrieves comment ID `8048b460-fe2b-11ec-b15d-779a7c8bbcc3` from case ID -`ecbf8a20-fe2a-11ec-b15d-779a7c8bbcc3`: - -[source,sh] --------------------------------------------------- -GET api/cases/ecbf8a20-fe2a-11ec-b15d-779a7c8bbcc3/comments/8048b460-fe2b-11ec-b15d-779a7c8bbcc3 --------------------------------------------------- -// KIBANA - -The API returns the requested comment JSON object. For example: - -[source,json] --------------------------------------------------- -{ - "id":"8048b460-fe2b-11ec-b15d-779a7c8bbcc3", - "version":"WzIzLDFd", - "type":"user", - "owner":"cases", - "comment":"A new comment", - "created_at":"2022-07-07T19:32:13.104Z", - "created_by":{ - "email":null, - "full_name":null, - "username":"elastic" - }, - "pushed_at":null, - "pushed_by":null, - "updated_at":null, - "updated_by":null -} --------------------------------------------------- \ No newline at end of file diff --git a/docs/api/cases/cases-api-get-configuration.asciidoc b/docs/api/cases/cases-api-get-configuration.asciidoc deleted file mode 100644 index 224134686c29c3..00000000000000 --- a/docs/api/cases/cases-api-get-configuration.asciidoc +++ /dev/null @@ -1,97 +0,0 @@ -[[cases-get-configuration]] -== Get case configuration API -++++ -Get configuration -++++ - -Retrieves external connection details, such as the closure type and -default connector for cases. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`GET :/api/cases/configure` - -`GET :/s//api/cases/configure` - -=== {api-prereq-title} - -You must have `read` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the case configuration. - -=== {api-path-parms-title} - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-query-parms-title} - -`owner`:: -(Optional, string or array of strings) A filter to limit the retrieved -details to a specific set of applications. Valid values are: `cases`, -`observability`, and `securitySolution`. If this parameter is omitted, the -response contains information for all applications that the user has access to -read. - -=== Response code - -`200`:: - Indicates a successful call. - -=== Example - -[source,sh] --------------------------------------------------- -GET api/cases/configure?owner=cases --------------------------------------------------- -// KIBANA - -The API returns the following type of information: - -[source,json] --------------------------------------------------- -[ - { - "closure_type": "close-by-user", - "owner": "cases", - "created_at": "2022-06-01T17:07:17.767Z", - "created_by": { - "email": "null", - "full_name": "null", - "username": "elastic" - }, - "updated_at": null, - "updated_by": null, - "connector": { - "id": "131d4448-abe0-4789-939d-8ef60680b498", - "name": "my-jira-connector", - "type": ".jira", - "fields": null - }, - "mappings": [ - { - "source": "title", - "target": "summary", - "action_type": "overwrite" - }, - { - "source": "description", - "target": "description", - "action_type": "overwrite" - }, - { - "source": "comments", - "target": "comments", - "action_type": "append" - } - ], - "version": "WzE3NywxXQ==", - "error": null, - "id": "7349772f-421a-4de3-b8bb-2d9b22ccee30" - } -] --------------------------------------------------- diff --git a/docs/api/cases/cases-api-get-reporters.asciidoc b/docs/api/cases/cases-api-get-reporters.asciidoc deleted file mode 100644 index 53edd3148a8ffc..00000000000000 --- a/docs/api/cases/cases-api-get-reporters.asciidoc +++ /dev/null @@ -1,63 +0,0 @@ -[[cases-api-get-reporters]] -== Get reporters API -++++ -Get reporters -++++ - -Returns information about the users who opened cases. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - - -=== {api-request-title} - -`GET :/api/cases/reporters` - -`GET :/s/api/cases/reporters` - -=== {api-prereq-title} - -You must have `read` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the cases. - -=== {api-description-title} - -The API returns information about the users as they existed at the time of the -case creation, including their name, full name, and email address. If any of -those details change thereafter or if a user is deleted, the information -returned by this API is unchanged. - -=== {api-query-parms-title} - -`owner`:: -(Optional, string or array of strings) A filter to limit the retrieved reporters -to a specific set of applications. If this parameter is omitted, the response -will contain all reporters from cases that the user has access to read. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Returns all case reporters: - -[source,sh] --------------------------------------------------- -GET api/cases/reporters --------------------------------------------------- -// KIBANA - -The API returns a JSON object with the retrieved reporters. For example: - -[source,json] --------------------------------------------------- -[ - {"username":"elastic","full_name":null,"email":null}, - {"username":"user1","full_name":"User 1","email":"user1@elastic.co"}, - {"username":"user2","full_name":"User 2","email":"user2@elastic.co"} -] --------------------------------------------------- diff --git a/docs/api/cases/cases-api-get-status.asciidoc b/docs/api/cases/cases-api-get-status.asciidoc deleted file mode 100644 index 3126398e019cbc..00000000000000 --- a/docs/api/cases/cases-api-get-status.asciidoc +++ /dev/null @@ -1,62 +0,0 @@ -[[cases-api-get-status]] -== Get case status API -++++ -Get case status -++++ - -Returns the number of cases that are open, closed, and in progress. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -deprecated::[8.1.0] - -=== {api-request-title} - -`GET :/api/cases/status` - -`GET :/s//api/cases/status` - -=== {api-prereq-title} - -You must have `read` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the cases you're seeking. - -=== {api-path-parms-title} - -:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-query-parms-title} - -`owner`:: -(Optional, string or array of strings) A filter to limit the retrieved case -statistics to a specific set of applications. Valid values are: `cases`, -`observability`, and `securitySolution`. If this parameter is omitted, the -response contains all cases that the user has access to read. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -GET api/cases/status --------------------------------------------------- -// KIBANA - -The API returns the following type of information: - -[source,json] --------------------------------------------------- -{ - "count_open_cases": 27, - "count_in_progress_cases": 50, - "count_closed_cases": 1198, -} --------------------------------------------------- diff --git a/docs/api/cases/cases-api-get-tags.asciidoc b/docs/api/cases/cases-api-get-tags.asciidoc deleted file mode 100644 index 74f1b4a551c573..00000000000000 --- a/docs/api/cases/cases-api-get-tags.asciidoc +++ /dev/null @@ -1,62 +0,0 @@ -[[cases-api-get-tag]] -== Get tags API -++++ -Get tags -++++ - -Aggregates and returns a list of case tags. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`GET :/api/cases/tags` - -`GET :/s//api/cases/tags` - -=== {api-prereq-title} - -You must have `read` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the cases you're seeking. - -=== {api-path-parms-title} - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-query-parms-title} - -`owner`:: -(Optional, string or array of strings) A filter to limit the retrieved tags to a -specific set of applications. Valid values are: `cases`, `observability`, and -`securitySolution`. If this parameter is omitted, the response contains tags -from all cases that the user has access to read. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -[source,sh] --------------------------------------------------- -GET api/cases/tags --------------------------------------------------- -// KIBANA - -The API returns a JSON object with tags from all the cases that the user has -access to read. For example: - -[source,json] --------------------------------------------------- -[ - "observability", - "security", - "tag 1", - "tag 2" -] --------------------------------------------------- diff --git a/docs/api/cases/cases-api-push.asciidoc b/docs/api/cases/cases-api-push.asciidoc deleted file mode 100644 index 1ac2cb6084ca22..00000000000000 --- a/docs/api/cases/cases-api-push.asciidoc +++ /dev/null @@ -1,116 +0,0 @@ -[[cases-api-push]] -== Push case API -++++ -Push case -++++ - -Pushes a case to an external service. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`POST :/api/cases//connector//_push` - -`POST :/s//api/cases//connector//_push` - -=== {api-prereq-title} - -You must have `all` privileges for the *{connectors-feature}* feature in the -*Management* section of the -<>. You must also have `all` -privileges for the *Cases* feature in the *Management*, *{observability}*, or -*Security* section of the {kib} feature privileges, depending on the -`owner` of the case you're pushing. - -=== {api-path-parms-title} - -``:: -(Required, string) The identifier for the case. To retrieve case IDs, use -<>. - -``:: -(Required, string) The identifier for the connector. To retrieve connector IDs, -use <>. - -:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Push the case to an external service: - -[source,sh] --------------------------------------------------- -POST api/cases/b917f300-0ed9-11ed-bd18-65557fe66949/connector/09f8c0b0-0eda-11ed-bd18-65557fe66949/_push -{} --------------------------------------------------- -// KIBANA - -The API returns a JSON object representing the pushed case. For example: - -[source,json] --------------------------------------------------- -{ - "id": "b917f300-0ed9-11ed-bd18-65557fe66949", - "version": "WzE3NjgsM10=", - "comments": [], - "totalComment": 0, - "totalAlerts": 0, - "description": "A case description.", - "title": "Case title 1", - "tags": [ - "tag 1" - ], - "settings": { - "syncAlerts": true - }, - "owner": "cases", - "duration": null, - "severity": "low", - "closed_at": null, - "closed_by": null, - "created_at": "2022-07-29T00:59:39.444Z", - "created_by": { - "username": "elastic", - "email": null, - "full_name": null - }, - "status": "open", - "updated_at": "2022-07-29T01:20:58.436Z", - "updated_by": { - "username": "elastic", - "full_name": null, - "email": null - }, - "connector": { - "id": "09f8c0b0-0eda-11ed-bd18-65557fe66949", - "name": "My connector", - "type": ".jira", - "fields": { - "issueType": "10006", - "parent": null, - "priority": "Low" - } - }, - "external_service": { - "pushed_at": "2022-07-29T01:20:58.436Z", - "pushed_by": { - "username": "elastic", - "full_name": null, - "email": null - }, - "connector_name": "My connector", - "external_id": "71926", - "external_title": "ES-554", - "external_url": "https://cases.jira.com", - "connector_id": "09f8c0b0-0eda-11ed-bd18-65557fe66949" - } -} --------------------------------------------------- diff --git a/docs/api/cases/cases-api-set-configuration.asciidoc b/docs/api/cases/cases-api-set-configuration.asciidoc deleted file mode 100644 index 7422b4dc0328c4..00000000000000 --- a/docs/api/cases/cases-api-set-configuration.asciidoc +++ /dev/null @@ -1,166 +0,0 @@ -[[cases-api-set-configuration]] -== Set case configuration API -++++ -Set configuration -++++ - -Sets external connection details, such as the closure type and -default connector for cases. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`POST :/api/cases/configure` - -`POST :/s//api/cases/configure` - -=== {api-prereq-title} - -You must have `all` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the case configuration. - -=== {api-description-title} - -Connectors are used to interface with external systems. You must create a -connector before you can use it in your cases. Refer to <>. - -If you set a default connector, it is automatically selected when you create -cases in {kib}. If you use the <>, however, -you must still specify all of the connector details. - -=== {api-path-parms-title} - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -[role="child_attributes"] -=== {api-request-body-title} - -`closure_type`:: -(Required, string) Specifies whether a case is automatically closed when it is -pushed to external systems. -+ --- -Valid values are: - -* `close-by-pushing`: Cases are automatically closed when they are pushed. -* `close-by-user`: Cases are not automatically closed. --- - -`connector`:: -(Required, object) An object that contains the connector configuration. -+ -.Properties of `connector` -[%collapsible%open] -==== -`fields`:: -(Required, object) An object that contains the connector fields. -+ --- -TIP: The fields specified in the case configuration are not used and are not -propagated to individual cases, therefore it is recommended to set it to `null`. --- - -`id`:: -(Required, string) The identifier for the connector. If you do not want a -default connector, use `none`. To retrieve connector IDs, use -<>. - -`name`:: -(Required, string) The name of the connector. If you do not want a default -connector, use `none`. To retrieve connector names, use -<>. - -`type`:: -(Required, string) The type of the connector. Valid values are: `.cases-webhook`, -`.jira`, `.none`, `.resilient`,`.servicenow`, `.servicenow-sir`, and `.swimlane`. -==== - -`owner`:: -(Required, string) The application that owns the case configuration. Valid -values are: `cases`, `observability`, or `securitySolution`. This value affects -whether you're setting case configuration details for {stack-manage-app}, -{observability}, or {security-app}. - -`settings`:: -(Optional, object) -An object that contains the case settings. -+ -.Properties of `settings` -[%collapsible%open] -==== -`syncAlerts`:: -(Required, boolean) Turns alert syncing on or off. -==== - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Sets the closure type and default connector for cases in **{stack-manage-app}**: - -[source,sh] --------------------------------------------------- -POST api/cases/configure -{ - "owner": "cases", - "connector": { - "id": "5e656730-e1ca-11ec-be9b-9b1838238ee6", - "name": "my-jira-connector", - "type": ".jira", - "fields": null, - }, - "closure_type": "close-by-user" -} --------------------------------------------------- - -The API returns the following response: - -[source,json] --------------------------------------------------- -{ - "closure_type": "close-by-user", - "owner": "cases", - "created_at": "2022-06-01T17:07:17.767Z", - "created_by": { - "username": "elastic", - "email": null, - "full_name": null - }, - "updated_at": null, - "updated_by": null, - "connector": { - "id": "5e656730-e1ca-11ec-be9b-9b1838238ee6", - "name": "my-jira-connector", - "type": ".jira", - "fields": null - }, - "mappings": [ - { - "source": "title", - "target": "summary", - "action_type": "overwrite" - }, - { - "source": "description", - "target": "description", - "action_type": "overwrite" - }, - { - "source": "comments", - "target": "comments", - "action_type": "append" - } - ], - "version": "WzIwNzMsMV0=", - "error": null, - "id": "4a97a440-e1cd-11ec-be9b-9b1838238ee6" -} --------------------------------------------------- diff --git a/docs/api/cases/cases-api-update-comment.asciidoc b/docs/api/cases/cases-api-update-comment.asciidoc deleted file mode 100644 index ccf5fe28f9ea0d..00000000000000 --- a/docs/api/cases/cases-api-update-comment.asciidoc +++ /dev/null @@ -1,193 +0,0 @@ -[[cases-api-update-comment]] -== Update case comment API -++++ -Update comment -++++ - -Updates a comment or alert in a case. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`PATCH :/api/cases//comments` - -`PATCH :/s//api/cases//comments` - -=== {api-prereq-title} - -You must have `all` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the case you're updating. - -=== {api-path-parms-title} - -``:: -The identifier for the case. To retrieve case IDs, use -<>. - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -[role="child_attributes"] -=== {api-request-body-title} - -`alertId`:: -(Required*, string or array of strings) The alert identifiers. It is -required only when `type` is `alert`. If you are adding multiple alerts to a -case, use an array of strings; `index` must also be an array with the same -length or number of elements in that case. Addings multiple alerts in this manner -is recommended rather than calling the API multiple times. - -`comment`:: -(Required*, string) The updated comment. It is required only when `type` is -`user`. - -`id`:: -(Required, string) The identifier for the comment. To retrieve comment IDs, use -<>. - -`index`:: -(Required*, string or array of strings) The alert indices. It is required only -when `type` is `alert`. If you are adding multiple alerts to a case, use an -array of strings; `alertId` must also be an array with the same length or number -of elements. preview:[] - -`owner`:: -(Required, string) The application that owns the case. It can be `cases`, -`observability`, or `securitySolution`. -+ -NOTE: You cannot change the owner of a comment. - -`rule`:: -(Required*, object) -The rule that is associated with the alert. It is required only when `type` is -`alert`. preview:[] -+ -.Properties of `rule` -[%collapsible%open] -==== -`id`:: -(Required, string) The rule identifier. preview:[] - -`name`:: -(Required, string) The rule name. preview:[] - -==== - -`type`:: -(Required, string) The comment type, which must be `user` or `alert`. -+ -NOTE: You cannot change the comment type. - -`version`:: -(Required, string) The current comment version. To retrieve version values, use -<>. - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Update comment ID `8af6ac20-74f6-11ea-b83a-553aecdb28b6` (associated with case -ID `293f1bc0-74f6-11ea-b83a-553aecdb28b6`): - -[source,sh] --------------------------------------------------- -PATCH api/cases/293f1bc0-74f6-11ea-b83a-553aecdb28b6/comments -{ - "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", - "version": "Wzk1LDFd", - "type": "user", - "comment": "An updated comment." -} --------------------------------------------------- -// KIBANA - -The API returns details about the case and its comments. For example: - -[source,json] --------------------------------------------------- -{ - "comments":[{ - "id": "8af6ac20-74f6-11ea-b83a-553aecdb28b6", - "version": "WzIwNjM3LDFd", - "comment": "An updated comment.", - "type": "user", - "owner": "cases", - "created_at": "2022-03-24T00:37:10.832Z", - "created_by": { - "email": null, - "full_name": null, - "username": "elastic" - }, - "pushed_at": null, - "pushed_by": null, - "updated_at": "2022-03-24T01:27:06.210Z", - "updated_by": { - "email": null, - "full_name": null, - "username": "elastic" - } - } - ], - "totalAlerts": 0, - "id": "293f1bc0-74f6-11ea-b83a-553aecdb28b6", - "version": "WzIwNjM2LDFd", - "totalComment": 1, - "title": "Case title 1", - "tags": ["tag 1"], - "description": "A case description.", - "settings": {"syncAlerts":false}, - "owner": "cases", - "duration": null, - "severity": "low", - "closed_at": null, - "closed_by": null, - "created_at": "2022-03-24T00:37:03.906Z", - "created_by": { - "email": null, - "full_name": null, - "username": "elastic" - }, - "status": "open", - "updated_at": "2022-03-24T01:27:06.210Z", - "updated_by": { - "email": null, - "full_name": null, - "username": "elastic" - }, - "connector": { - "id": "none", - "name": "none", - "type": ".none", - "fields": null - }, - "external_service": null -} --------------------------------------------------- - -Update an alert in the case: - -[source,sh] --------------------------------------------------- -PATCH api/cases/293f1bc0-74f6-11ea-b83a-553aecdb28b6/comments -{ - "id": "73362370-ab1a-11ec-985f-97e55adae8b9", - "version": "WzMwNDgsMV0=", - "type": "alert", - "owner": "cases", - "alertId": "c8789278659fdf88b7bf7709b90a082be070d0ba4c23c9c4b552e476c2a667c4", - "index": ".internal.alerts-security.alerts-default-000001", - "rule": - { - "id":"94d80550-aaf4-11ec-985f-97e55adae8b9", - "name":"security_rule" - } -} --------------------------------------------------- -// KIBANA diff --git a/docs/api/cases/cases-api-update-configuration.asciidoc b/docs/api/cases/cases-api-update-configuration.asciidoc deleted file mode 100644 index 9cc913adadc64e..00000000000000 --- a/docs/api/cases/cases-api-update-configuration.asciidoc +++ /dev/null @@ -1,132 +0,0 @@ -[[cases-api-update-configuration]] -== Update case configuration API -++++ -Update configuration -++++ - -Updates external connection details, such as the closure type and default -connector for cases. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`PATCH :/api/cases/configure/` - -`PATCH :/s//api/cases/configure/` - -=== {api-prereq-title} - -You must have `all` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the case configuration. - -=== {api-description-title} - -Connectors are used to interface with external systems. You must create a -connector before you can it in your cases. Refer to <>. - -=== {api-path-parms-title} - -``:: -The identifier for the configuration. To retrieve the configuration IDs, use -<>. - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -[role="child_attributes"] -=== Request body - -`closure_type`:: -(Optional, string) Determines whether a case is automatically closed when it is -pushed to external systems. Valid values are: -+ --- -* `close-by-pushing`: Cases are automatically closed when they -are pushed. -* `close-by-user`: Cases are not automatically closed. --- - -`connector`:: -(Optional, object) An object that contains the connector configuration. -+ -.Properties of `connector` -[%collapsible%open] -==== -`fields`:: -(Required, object) An object that contains the connector fields. -+ --- -TIP: The fields specified in the case configuration are not used and are not -propagated to individual cases, therefore it is recommended to set it to `null`. --- - -`id`:: -(Required, string) The identifier for the connector. To retrieve connector IDs, -use <>. - -`name`:: -(Required, string) The name of the connector. - -`type`:: -(Required, string) The type of the connector. Valid values are: `.cases-webhook`, -`.jira`, `.none`, `.resilient`,`.servicenow`, `.servicenow-sir`, and `.swimlane`. -==== - -`version`:: -(Required, string) The version of the connector. To retrieve the version value, -use <>. - -=== Response code - -`200`:: - Indicates a successful call. - -=== Example - -Change the closure type configuration option: - -[source,sh] --------------------------------------------------- -PATCH api/cases/configure/3297a0f0-b5ec-11ec-b141-0fdb20a7f9a9 -{ - "closure_type": "close-by-pushing", - "version": "WzIwMiwxXQ==" -} --------------------------------------------------- -// KIBANA - -The API returns the following: - -[source,json] --------------------------------------------------- -{ - "closure_type": "close-by-pushing", - "owner": "cases", - "created_at": "2022-06-01T17:07:17.767Z", - "created_by": { - "email": "null", - "full_name": "null", - "username": "elastic" - }, - "updated_at": "2022-06-01T19:58:48.169Z", - "updated_by": { - "email": "null", - "full_name": "null", - "username": "elastic" - }, - "connector": { - "id": "none", - "name": "none", - "type": ".none", - "fields": null - }, - "mappings": [], - "version": "WzkwNiw1XQ==", - "error": null, - "id": "3297a0f0-b5ec-11ec-b141-0fdb20a7f9a9" -} --------------------------------------------------- diff --git a/docs/api/cases/cases-api-update.asciidoc b/docs/api/cases/cases-api-update.asciidoc deleted file mode 100644 index 8400a33c8c6dd2..00000000000000 --- a/docs/api/cases/cases-api-update.asciidoc +++ /dev/null @@ -1,285 +0,0 @@ -[[cases-api-update]] -== Update cases API -++++ -Update cases -++++ - -Updates one or more cases. - -NOTE: For the latest details, refer to {api-kibana}/group/endpoint-cases[cases APIs]. - -=== {api-request-title} - -`PATCH :/api/cases` - -`PATCH :/s//api/cases` - -=== {api-prereq-title} - -You must have `all` privileges for the *Cases* feature in the *Management*, -*{observability}*, or *Security* section of the -<>, depending on the -`owner` of the cases you're updating. - -=== {api-path-parms-title} - -``:: -(Optional, string) An identifier for the space. If it is not specified, the -default space is used. - -[role="child_attributes"] -=== {api-request-body-title} - -`cases`:: -(Required, array of objects) Array containing one or more case objects. -+ -.Properties of `cases` objects -[%collapsible%open] -==== - -`assignees`:: -(Optional, array of objects) Array containing users that are assigned to the case. -+ -.Properties of assignee objects -[%collapsible%open] -===== -`uid`:: -(Required, string) A unique identifier for the user profile. These identifiers -can be found by using the -{ref}/security-api-suggest-user-profile.html[suggest user profile API]. -===== - -`connector`:: -(Optional, object) An object that contains the connector configuration. -+ -.Properties of `connector` -[%collapsible%open] -===== -`fields`:: -(Required, object) An object containing the connector fields. To remove the -connector, specify `null`. If you want to omit any individual field, specify -`null` as its value. -+ -.Properties of `fields` -[%collapsible%open] -======= -For {ibm-r} connectors, specify: - -`issueTypes`::: -(Required, array of strings) The issue types of the issue. - -`severityCode`::: -(Required, string) The severity code of the issue. - -For {jira} connectors, specify: - -`issueType`::: -(Required, string) The issue type of the issue. - -`parent`::: -(Required, string) The key of the parent issue, when the issue type is -`Sub-task`. - -`priority`::: -(Required, string) The priority of the issue. - -For {sn-itsm} connectors, specify: - -`category`::: -(Required, string) The category of the incident. - -`impact`::: -(Required, string) The effect an incident had on business. - -`severity`::: -(Required, string) The severity of the incident. - -`subcategory`::: -(Required, string) The subcategory of the incident. - -`urgency`::: -(Required, string) The extent to which the incident resolution can be delayed. - -For {sn-sir} connectors, specify: - -`category`::: -(Required, string) The category of the incident. - -`destIp`::: -(Required, string) A comma separated list of destination IPs. - -`malwareHash`::: -(Required, string) A comma separated list of malware hashes. - -`malwareUrl`::: -(Required, string) A comma separated list of malware URLs. - -`priority`::: -(Required, string) The priority of the incident. - -`sourceIp`::: -(Required, string) A comma separated list of source IPs. - -`subcategory`::: -(Required, string) The subcategory of the incident. - -For {swimlane} connectors, specify: - -`caseId`::: -(Required, string) The identifier for the case. - -For {webhook-cm} connectors, specify `null`. -======= - -`id`:: -(Required, string) The identifier for the connector. To remove the connector, -use `none`. To retrieve connector IDs, use <>). - -`name`:: -(Required, string) The name of the connector. To remove the connector, use -`none`. - -`type`:: -(Required, string) The type of the connector. Valid values are: `.cases-webhook`, -`.jira`, `.none`, `.resilient`,`.servicenow`, `.servicenow-sir`, and `.swimlane`. -To remove the connector, use `.none`. - -===== - -`description`:: -(Optional, string) The updated case description. - -`id`:: -(Required, string) The identifier for the case. - -`settings`:: -(Optional, object) -An object that contains the case settings. -+ -.Properties of `settings` -[%collapsible%open] -===== -`syncAlerts`:: -(Required, boolean) Turn on or off synching with alerts. -===== - -`severity`:: -(Optional,string) The severity of the case. Valid values are: `critical`, `high`, -`low`, and `medium`. - -`status`:: -(Optional, string) The case status. Valid values are: `closed`, `in-progress`, -and `open`. - -`tags`:: -(Optional, string array) The words and phrases that help categorize cases. - -`title`:: -(Optional, string) A title for the case. - -`version`:: -(Required, string) The current version of the case. To determine this value, use -<> or <>. -==== - -=== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -=== {api-examples-title} - -Update the description, tags, and connector of case ID -`a18b38a0-71b0-11ea-a0b2-c51ea50a58e2`: - -[source,sh] --------------------------------------------------- -PATCH api/cases -{ - "cases": [ - { - "id": "a18b38a0-71b0-11ea-a0b2-c51ea50a58e2", - "version": "WzIzLDFd", - "connector": { - "id": "131d4448-abe0-4789-939d-8ef60680b498", - "name": "My connector", - "type": ".jira", - "fields": { - "issueType": "10006", - "priority": null, - "parent": null - } - }, - "description": "A new description.", - "tags": [ "tag-1", "tag-2" ], - "assignees": [], - "settings": { - "syncAlerts": true - } - } - ] -} --------------------------------------------------- -// KIBANA - -The API returns the updated case with a new `version` value. For example: - -[source,json] --------------------------------------------------- -[ - { - "id": "66b9aa00-94fa-11ea-9f74-e7e108796192", - "version": "WzU0OCwxXQ==", - "comments": [], - "totalComment": 0, - "totalAlerts": 0, - "title": "Case title 1", - "tags": [ "tag-1", "tag-2" ], - "settings": { - "syncAlerts": true - }, - "owner": "cases", - "description": "A new description.", - "duration": null, - "severity": "low", - "closed_at": null, - "closed_by": null, - "created_at": "2022-05-13T09:16:17.416Z", - "created_by": { - "email": null, - "full_name": null, - "username": "elastic" - }, - "status": "open", - "updated_at": "2022-05-13T09:48:33.043Z", - "updated_by": { - "email": null, - "full_name": null, - "username": "elastic" - }, - "connector": { - "id": "131d4448-abe0-4789-939d-8ef60680b498", - "name": "My connector", - "type": ".jira", - "fields": { - "issueType": "10006", - "parent": null, - "priority": null, - } - }, - "external_service": { - "external_title": "IS-4", - "pushed_by": { - "full_name": null, - "email": null, - "username": "elastic" - }, - "external_url": "https://hms.atlassian.net/browse/IS-4", - "pushed_at": "2022-05-13T09:20:40.672Z", - "connector_id": "05da469f-1fde-4058-99a3-91e4807e2de8", - "external_id": "10003", - "connector_name": "Jira" - } - } -] --------------------------------------------------- diff --git a/docs/api/machine-learning.asciidoc b/docs/api/machine-learning.asciidoc index 265896e6340df0..33abb719da9f07 100644 --- a/docs/api/machine-learning.asciidoc +++ b/docs/api/machine-learning.asciidoc @@ -1,11 +1,4 @@ [[machine-learning-api]] == {ml-cap} APIs -//Manage {kib} saved objects, including dashboards, visualizations, and more. - -The following {ml} API is available: - -* <> -//to retrieve a single {kib} saved object by ID - -include::machine-learning/sync.asciidoc[] +For the latest details, refer to {api-kibana}/group/endpoint-ml[machine learning APIs]. diff --git a/docs/api/machine-learning/sync.asciidoc b/docs/api/machine-learning/sync.asciidoc deleted file mode 100644 index bd65ce56dd5d1c..00000000000000 --- a/docs/api/machine-learning/sync.asciidoc +++ /dev/null @@ -1,95 +0,0 @@ -[[machine-learning-api-sync]] -=== Sync {ml} saved objects API -++++ -Sync {ml} saved objects -++++ - -Synchronizes {kib} saved objects for {ml} jobs and trained models. - -[NOTE] -==== -For the most up-to-date API details, refer to the -{kib-repo}/tree/{branch}/x-pack/plugins/ml/common/openapi[open API specification]. -==== - -[[machine-learning-api-sync-request]] -==== {api-request-title} - -`GET :/api/ml/saved_objects/sync` - -`GET :/s//api/ml/saved_objects/sync` - -[[machine-learning-api-sync-prereq]] -==== {api-prereq-title} - -You must have `all` privileges for the *Machine Learning* feature in the *Analytics* section of the -<>. - -[[machine-learning-api-sync-desc]] -==== {api-description-title} - -This API runs automatically when you start {kib} and periodically thereafter. - -[[machine-learning-api-sync-path-params]] -==== {api-path-parms-title} - -`space_id`:: -(Optional, string) An identifier for the space. If `space_id` is not provided in -the URL the default space is used. - -[[machine-learning-api-sync-query-params]] -==== {api-query-parms-title} - -`simulate`:: -(Optional, boolean) When `true`, simulates the synchronization by only returning -the list actions that _would_ be performed. - -[[machine-learning-api-sync-response-body]] -==== {api-response-body-title} - -`datafeedsAdded`:: -(array) If a saved object for an {anomaly-job} is missing a {dfeed} identifier, -it is added. This list contains the {dfeed} identifiers and indicates whether -the synchronization was successful. - -`datafeedsRemoved`:: -(array) If a saved object for an anomaly detection job references a datafeed -that no longer exists, it is deleted. This list contains the {dfeed} identifiers -and indicates whether the synchronization was successful. - -`savedObjectsCreated`:: -(array) If saved objects are missing for {ml} jobs or trained models, they are -created. This list contains the job and model identifiers and indicates whether -the synchronization was successful. - -`savedObjectsDeleted`:: -(array) If saved objects exist for {ml} jobs or trained models that no longer -exist, they are deleted. This list contains the job and model identifiers and -indicates whether the synchronization was successful. - -[[machine-learning-api-sync-codes]] -==== {api-response-codes-title} - -`200`:: - Indicates a successful call. - -[[machine-learning-api-sync-example]] -==== {api-examples-title} - -Retrieve the list of {ml} saved objects that require synchronization: - -[source,sh] --------------------------------------------------- -GET api/ml/saved_objects/sync?simulate=true --------------------------------------------------- -// KIBANA - -If there are two jobs that need to be synchronized, for example, the API returns -the following response: - -[source,sh] --------------------------------------------------- -{"savedObjectsCreated":{"anomaly_detector":{"myjob1":{"success":true},"myjob2":{"success":true}}},"savedObjectsDeleted":{},"datafeedsAdded":{},"datafeedsRemoved":{}} --------------------------------------------------- - -To perform the synchronization, re-run the API and omit the `simulate` parameter. \ No newline at end of file diff --git a/docs/api/role-management/put-bulk.asciidoc b/docs/api/role-management/put-bulk.asciidoc index e41b836e263579..a11de47167e052 100644 --- a/docs/api/role-management/put-bulk.asciidoc +++ b/docs/api/role-management/put-bulk.asciidoc @@ -189,7 +189,7 @@ Grant full access to all features in the Default space for `my_kibana_role_1` an [source,sh] -------------------------------------------------- -$ curl -X PUT api/security/role +$ curl -X POST api/security/roles { "roles": { "my_kibana_role_1": { diff --git a/docs/management/connectors/action-types/bedrock.asciidoc b/docs/management/connectors/action-types/bedrock.asciidoc index ab08e697daa532..8f0728a034615d 100644 --- a/docs/management/connectors/action-types/bedrock.asciidoc +++ b/docs/management/connectors/action-types/bedrock.asciidoc @@ -9,7 +9,7 @@ :frontmatter-tags-user-goals: [configure] -The {bedrock} connector uses https://github.com/axios/axios[axios] to send a POST request to {bedrock}. The connector uses the <> to send the request. +The {bedrock} connector uses https://github.com/axios/axios[axios] to send a POST request to {bedrock}. [float] [[define-bedrock-ui]] @@ -37,8 +37,7 @@ Secret:: The secret for authentication. [[bedrock-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/bedrock-params.png[{bedrock} params test] diff --git a/docs/management/connectors/action-types/cases-webhook.asciidoc b/docs/management/connectors/action-types/cases-webhook.asciidoc index 3d5bfecffcb3b3..e68b820101b8ff 100644 --- a/docs/management/connectors/action-types/cases-webhook.asciidoc +++ b/docs/management/connectors/action-types/cases-webhook.asciidoc @@ -192,8 +192,7 @@ The username for HTTP basic authentication. [[cases-webhook-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/cases-webhook-test.gif[{webhook-cm} params test] diff --git a/docs/management/connectors/action-types/d3security.asciidoc b/docs/management/connectors/action-types/d3security.asciidoc index 76152365f76cdb..5eb23cc356a95b 100644 --- a/docs/management/connectors/action-types/d3security.asciidoc +++ b/docs/management/connectors/action-types/d3security.asciidoc @@ -8,7 +8,7 @@ :frontmatter-tags-content-type: [how-to] :frontmatter-tags-user-goals: [configure] -The D3 Security connector uses https://github.com/axios/axios[axios] to send a POST request to a D3 Security endpoint. The connector uses the <> to send the request. You can use the connector for rule actions. +The D3 Security connector uses https://github.com/axios/axios[axios] to send a POST request to a D3 Security endpoint. You can use the connector for rule actions. To create this connector, you must first configure a webhook key in your D3 SOAR environment. For configuration tips, refer to <>. @@ -35,8 +35,7 @@ Token:: The D3 Security token. [[d3security-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/d3security-params-test.png[D3 Security params test] diff --git a/docs/management/connectors/action-types/gemini.asciidoc b/docs/management/connectors/action-types/gemini.asciidoc index 610fb4ad48f155..2371015975d92e 100644 --- a/docs/management/connectors/action-types/gemini.asciidoc +++ b/docs/management/connectors/action-types/gemini.asciidoc @@ -9,7 +9,7 @@ :frontmatter-tags-user-goals: [configure] -The {gemini} connector uses https://github.com/axios/axios[axios] to send a POST request to {gemini}. The connector uses the <> to send the request. +The {gemini} connector uses https://github.com/axios/axios[axios] to send a POST request to {gemini}. [float] [[define-gemini-ui]] @@ -38,8 +38,7 @@ Credentials JSON:: The GCP service account JSON file for authentication. [[gemini-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/gemini-params.png[{gemini} params test] diff --git a/docs/management/connectors/action-types/jira.asciidoc b/docs/management/connectors/action-types/jira.asciidoc index abff601184c583..906a2945d82de6 100644 --- a/docs/management/connectors/action-types/jira.asciidoc +++ b/docs/management/connectors/action-types/jira.asciidoc @@ -43,8 +43,7 @@ API token:: Jira API authentication token for HTTP Basic authentication. [[jira-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/jira-params-test.png[Jira params test] diff --git a/docs/management/connectors/action-types/openai.asciidoc b/docs/management/connectors/action-types/openai.asciidoc index 968f5a1e30f3a6..9b468d7ca09bcf 100644 --- a/docs/management/connectors/action-types/openai.asciidoc +++ b/docs/management/connectors/action-types/openai.asciidoc @@ -9,8 +9,7 @@ :frontmatter-tags-user-goals: [configure] -The OpenAI connector uses https://github.com/axios/axios[axios] to send a POST request to an OpenAI provider, either OpenAI or Azure OpenAI. The connector uses the <> to send the request. - +The OpenAI connector uses https://github.com/axios/axios[axios] to send a POST request to an OpenAI provider, either OpenAI or Azure OpenAI. [float] [[define-gen-ai-ui]] === Create connectors in {kib} @@ -48,8 +47,7 @@ API key:: The OpenAI or Azure OpenAI API key for authentication. [[gen-ai-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/gen-ai-params-test.png[OpenAI params test] diff --git a/docs/management/connectors/action-types/servicenow-itom.asciidoc b/docs/management/connectors/action-types/servicenow-itom.asciidoc index 2dc62667453365..3cf64f7cc3b069 100644 --- a/docs/management/connectors/action-types/servicenow-itom.asciidoc +++ b/docs/management/connectors/action-types/servicenow-itom.asciidoc @@ -61,8 +61,7 @@ The username for HTTP basic authentication. [[servicenow-itom-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/servicenow-itom-params-test.png[{sn-itom} params test] diff --git a/docs/management/connectors/action-types/servicenow-sir.asciidoc b/docs/management/connectors/action-types/servicenow-sir.asciidoc index da2b37dc1f5586..164fe70a6d55b7 100644 --- a/docs/management/connectors/action-types/servicenow-sir.asciidoc +++ b/docs/management/connectors/action-types/servicenow-sir.asciidoc @@ -61,8 +61,7 @@ The username for HTTP basic authentication. [[servicenow-sir-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/servicenow-sir-params-test.png[{sn-sir} params test] diff --git a/docs/management/connectors/action-types/swimlane.asciidoc b/docs/management/connectors/action-types/swimlane.asciidoc index 227846c7539f3c..368ca9f5ecfdf0 100644 --- a/docs/management/connectors/action-types/swimlane.asciidoc +++ b/docs/management/connectors/action-types/swimlane.asciidoc @@ -35,8 +35,7 @@ API token:: Swimlane API authentication token for HTTP basic authentication. [[swimlane-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/swimlane-params-test.png[Swimlane params test] diff --git a/docs/management/connectors/action-types/teams.asciidoc b/docs/management/connectors/action-types/teams.asciidoc index 174b0173cb08a8..97d953c13eb112 100644 --- a/docs/management/connectors/action-types/teams.asciidoc +++ b/docs/management/connectors/action-types/teams.asciidoc @@ -19,6 +19,7 @@ or as needed when you're creating a rule. For example: [role="screenshot"] image::management/connectors/images/teams-connector.png[Teams connector] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. [float] [[teams-connector-configuration]] @@ -27,21 +28,21 @@ image::management/connectors/images/teams-connector.png[Teams connector] Microsoft Teams connectors have the following configuration properties: Name:: The name of the connector. -Webhook URL:: The URL of the incoming webhook. See https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook#add-an-incoming-webhook-to-a-teams-channel[Add Incoming Webhooks] for instructions on generating this URL. If you are using the <> setting, make sure the hostname is added to the allowed hosts. +Webhook URL:: The URL of the incoming webhook. Refer to <>. If you are using the <> setting, make sure the hostname is added to the allowed hosts. [float] [[teams-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/teams-params-test.png[Teams params test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. Microsoft Teams actions have the following properties. -Message:: The message text, converted to the `text` field in the Webhook JSON payload. Currently only the text field is supported. Markdown, images, and other advanced formatting are not yet supported. +Message:: The message text. Markdown, images, and other advanced formatting are not yet supported. [float] [[teams-connector-networking-configuration]] @@ -51,19 +52,40 @@ Use the <> to customize connecto [float] [[configuring-teams]] -=== Configure a Microsoft Teams account +=== Configure Microsoft Teams -You need a https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook[Microsoft Teams webhook URL] to -configure a Microsoft Teams action. To create a webhook -URL, add the **Incoming Webhook App** through the Microsoft Teams console: +Microsoft 365 connectors are being https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/[retired]; you must use the *Workflows* app to create a webhook URL. +For example: -. Log in to http://teams.microsoft.com[teams.microsoft.com] as a team administrator. -. Navigate to the Apps directory, search for and select the *Incoming Webhook* app. -. Choose _Add to team_ and select a team and channel for the app. -. Enter a name for your webhook and (optionally) upload a custom icon. +. Go to the *Workflows* app in Teams and select the *Create* tab. +. Create a workflow from a blank template. + -image::images/teams-add-webhook-integration.png[] -. Click *Create*. -. Copy the generated webhook URL so you can paste it into your Teams connector form. +-- +NOTE: You cannot use the "Post to a channel when a webhook request is received" template; it does not work with the Microsoft Teams connector. +-- +. Add a trigger as the first step in the workflow. Search for webhook triggers and select *When a Teams webhook request is received*. Set *Who can trigger the flow?* to `Anyone`. +. Add *Post message in a chat or channel* as the second step in the workflow. +.. Set *Post as* to `Flow bot`. +.. Set *Post in* to `Channel`. +.. Set *Team* and *Channel* to the appropriate values for where you want the message displayed. + -image::images/teams-copy-webhook-url.png[] +-- +NOTE: Per https://learn.microsoft.com/en-us/power-automate/teams/send-a-message-in-teams#known-issues-and-limitations, you cannot use a private channel. +-- +.. Set the *Message* to `@{triggerBody()?['text']}`. ++ +-- +Alternatively, you can add a *Parse JSON* step before the *Post message in a chat or channel* step in the workflow. Set the *Content* to `Body` and the *Schema* to the following value: + +```json +{ + "type": "object", + "properties": { + "text": { + "type": "string" + } + } +} +``` +-- +. Save the workflow and copy the HTTP POST URL from the first step. This is the URL required by the Microsoft Teams connector. \ No newline at end of file diff --git a/docs/management/connectors/action-types/thehive.asciidoc b/docs/management/connectors/action-types/thehive.asciidoc index 9ee6a2e4325c13..302bd142031eee 100644 --- a/docs/management/connectors/action-types/thehive.asciidoc +++ b/docs/management/connectors/action-types/thehive.asciidoc @@ -36,8 +36,7 @@ API Key:: TheHive API key for authentication. [[TheHive-action-configuration]] === Test connectors -You can test connectors for creating a case or an alert with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/thehive-params-case-test.png[TheHive case params test] diff --git a/docs/management/connectors/action-types/tines.asciidoc b/docs/management/connectors/action-types/tines.asciidoc index c9888ab7b9087e..6f219f2aa4572f 100644 --- a/docs/management/connectors/action-types/tines.asciidoc +++ b/docs/management/connectors/action-types/tines.asciidoc @@ -35,8 +35,7 @@ API Token:: A Tines API token created by the user. For more information, refer [[tines-action-parameters]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/tines-params-test.png[Tines params test] diff --git a/docs/management/connectors/action-types/torq.asciidoc b/docs/management/connectors/action-types/torq.asciidoc index 7b4b9712adcb98..cac39ae6799bd0 100644 --- a/docs/management/connectors/action-types/torq.asciidoc +++ b/docs/management/connectors/action-types/torq.asciidoc @@ -35,8 +35,7 @@ Torq authentication header secret:: Secret of the webhook authentication header. [[torq-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/torq-connector-test.png[Torq connector test] diff --git a/docs/management/connectors/action-types/webhook.asciidoc b/docs/management/connectors/action-types/webhook.asciidoc index d9a4dd43daeafb..eda17fdcc319a1 100644 --- a/docs/management/connectors/action-types/webhook.asciidoc +++ b/docs/management/connectors/action-types/webhook.asciidoc @@ -55,8 +55,7 @@ Controls the certificate verification. [[webhook-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/webhook-params-test.png[Webhook params test] diff --git a/docs/management/connectors/action-types/xmatters.asciidoc b/docs/management/connectors/action-types/xmatters.asciidoc index ebc230bf0b39b9..f3e1fa3c2b634d 100644 --- a/docs/management/connectors/action-types/xmatters.asciidoc +++ b/docs/management/connectors/action-types/xmatters.asciidoc @@ -44,8 +44,7 @@ Password:: Password for HTTP basic authentication. [[xmatters-action-configuration]] === Test connectors -You can test connectors with the <> or -as you're creating or editing the connector in {kib}. For example: +You can test connectors as you're creating or editing the connector in {kib}. For example: [role="screenshot"] image::management/connectors/images/xmatters-params-test.png[xMatters params test] diff --git a/docs/management/connectors/images/teams-add-webhook-integration.png b/docs/management/connectors/images/teams-add-webhook-integration.png deleted file mode 100644 index a2d070cb337433..00000000000000 Binary files a/docs/management/connectors/images/teams-add-webhook-integration.png and /dev/null differ diff --git a/docs/management/connectors/images/teams-connector.png b/docs/management/connectors/images/teams-connector.png index 4b9112db28474d..3164bd4be74380 100644 Binary files a/docs/management/connectors/images/teams-connector.png and b/docs/management/connectors/images/teams-connector.png differ diff --git a/docs/management/connectors/images/teams-copy-webhook-url.png b/docs/management/connectors/images/teams-copy-webhook-url.png deleted file mode 100644 index adb455c64cbf09..00000000000000 Binary files a/docs/management/connectors/images/teams-copy-webhook-url.png and /dev/null differ diff --git a/docs/management/connectors/images/teams-params-test.png b/docs/management/connectors/images/teams-params-test.png index 01efb84ae60d46..60bf87394bb683 100644 Binary files a/docs/management/connectors/images/teams-params-test.png and b/docs/management/connectors/images/teams-params-test.png differ diff --git a/docs/management/maintenance-windows/maintenance-windows.asciidoc b/docs/management/maintenance-windows/maintenance-windows.asciidoc index 559151c060ed27..590f9d8da88385 100644 --- a/docs/management/maintenance-windows/maintenance-windows.asciidoc +++ b/docs/management/maintenance-windows/maintenance-windows.asciidoc @@ -46,7 +46,7 @@ image::images/create-maintenance-window.png[The Create Maintenance Window user i By default, maintenance windows affect all categories of rules. The category-specific maintenance window options alter this behavior. -For the definitive list of rule types in each category, refer to the <>. +For the definitive list of rule types in each category, refer to the {api-kibana}/group/endpoint-alerting[get rule types API]. If you turn on *Filter alerts*, you can use KQL to filter the alerts affected by the maintenance window: diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 06a2fc7886b585..aa5e2390c95188 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -765,3 +765,223 @@ Refer to {observability-guide}/apm-agent-key-api.html[APM agent Key API]. Refer to {observability-guide}/apm-app-troubleshooting.html[Troubleshooting]. :!apm-docs-move-notice: + +[role="exclude",id="create-rule-api"] +== Create rule API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="delete-rule-api"] +== Delete rule API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="disable-rule-api"] +== Disable rule API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="enable-rule-api"] +== Enable rule API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="find-rules-api"] +== Find rules API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="get-rule-api"] +== Get rule API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="get-alerting-framework-health-api"] +== Get alerting framework health API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="list-rule-types-api"] +== Get rule types API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="mute-alert-api"] +== Mute alert API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="mute-all-alerts-api"] +== Mute all alerts API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="unmute-alert-api"] +== Unmute alert API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="unmute-all-alerts-api"] +== Unmute all alerts API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="update-rule-api"] +== Update rule API + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="alerts-api"] +== Deprecated 7.x APIs + +Refer to {api-kibana}/group/endpoint-alerting[alerting API]. + +[role="exclude",id="update-connector-api"] +== Update connector API + +Refer to {api-kibana}/group/endpoint-connectors[connectors API]. + +[role="exclude",id="list-connector-types-api"] +== List connector types API + +Refer to {api-kibana}/group/endpoint-connectors[connectors API]. + +[role="exclude",id="get-connector-api"] +== Get connector API + +Refer to {api-kibana}/group/endpoint-connectors[connectors API]. + +[role="exclude",id="get-all-connectors-api"] +== Get all connectors API + +Refer to {api-kibana}/group/endpoint-connectors[connectors API]. + +[role="exclude",id="execute-connector-api"] +== Run connector API + +Refer to {api-kibana}/group/endpoint-connectors[connectors API]. + +[role="exclude",id="delete-connector-api"] +== Delete connector API + +Refer to {api-kibana}/group/endpoint-connectors[connectors API]. + +[role="exclude",id="create-connector-api"] +== Create connector API + +Refer to {api-kibana}/group/endpoint-connectors[connectors API]. + +[role="exclude",id="actions-and-connectors-legacy-apis"] +== Deprecated 7.x APIs + +Refer to {api-kibana}/group/endpoint-connectors[connectors API]. + +[role="exclude",id="cases-api-add-comment"] +== Add comment to case API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-create"] +== Create case API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-delete-cases"] +== Delete cases API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-delete-comments"] +== Delete comments from case API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-find-case-activity"] +== Find case activity API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-find-cases"] +== Find cases API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-find-connectors"] +== Find connectors API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-get-alerts"] +== Get alerts attached to case API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-get-case-activity"] +== Get case activity API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-get-case"] +== Get case API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-get-cases-by-alert"] +== Get cases by alert API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-get-comments"] +== Get comments API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-get-configuration"] +== Get case configuration API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-get-reporters"] +== Get reporters API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-get-status"] +== Get case status API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-get-tag"] +== Get tags API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-push"] +== Push case API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-set-configuration"] +== Set case configuration API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-update-comment"] +== Update case comment API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-update-configuration"] +== Update case configuration API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="cases-api-update"] +== Update cases API + +Refer to {api-kibana}/group/endpoint-cases[cases API]. + +[role="exclude",id="machine-learning-api-sync"] +== Sync {ml} saved objects API + +Refer to {api-kibana}/group/endpoint-ml[machine learning APIs]. \ No newline at end of file diff --git a/docs/setup/upgrade/resolving-migration-failures.asciidoc b/docs/setup/upgrade/resolving-migration-failures.asciidoc index d81a577a19a88f..596674237eb7a5 100644 --- a/docs/setup/upgrade/resolving-migration-failures.asciidoc +++ b/docs/setup/upgrade/resolving-migration-failures.asciidoc @@ -18,24 +18,6 @@ When you address the root cause for the migration failure, {kib} automatically retries the migration. If you're unable to resolve a failed migration, contact Support. - -[float] -==== Known issues with {fleet} beta -If you see a`timeout_exception` or `receive_timeout_transport_exception` error, -it might be from a known known issue in 7.12.0 if you tried the {fleet} beta. -Upgrade migrations fail because of a large number of documents in the `.kibana` index, -which causes {kib} to log errors such as: - -[source,sh] --------------------------------------------- -Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [receive_timeout_transport_exception]: [instance-0000000002][10.32.1.112:19541][cluster:monitor/task/get] request_id [2648] timed out after [59940ms] - -Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [timeout_exception]: Timed out waiting for completion of [org.elasticsearch.index.reindex.BulkByScrollTask@6a74c54] --------------------------------------------- - -For instructions on how to mitigate the known issue, refer to https://github.com/elastic/kibana/issues/95321[the GitHub issue]. - - [float] ==== Corrupt saved objects To find and remedy problems caused by corrupt documents, we highly recommend testing your {kib} upgrade in a development cluster, @@ -56,7 +38,7 @@ To delete the documents that cause migrations to fail, take the following steps: . Create a role as follows: + -[source,sh] +[source,console] -------------------------------------------- PUT _security/role/grant_kibana_system_indices { @@ -76,7 +58,7 @@ PUT _security/role/grant_kibana_system_indices . Create a user with the role above and `superuser` built-in role: + -[source,sh] +[source,console] -------------------------------------------- POST /_security/user/temporarykibanasuperuser { @@ -87,7 +69,7 @@ POST /_security/user/temporarykibanasuperuser . Remove the write block which the migration system has placed on the previous index: + -[source,sh] +[source,console] -------------------------------------------- PUT .kibana_7.12.1_001/_settings { @@ -99,7 +81,7 @@ PUT .kibana_7.12.1_001/_settings . Delete the corrupt document: + -[source,sh] +[source,console] -------------------------------------------- DELETE .kibana_7.12.0_001/_doc/marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275 -------------------------------------------- @@ -185,7 +167,7 @@ The process is waiting for a yellow index status. There are two known causes: Before retrying the migration, inspect the output of the `_cluster/allocation/explain?index=${targetIndex}` API to identify why the index isn't yellow: -[source,sh] +[source,console] -------------------------------------------- GET _cluster/allocation/explain { @@ -220,7 +202,7 @@ Unable to complete saved object migrations for the [.kibana] index: [incompatibl -------------------------------------------- To get around the issue, remove the transient and persisted routing allocation settings: -[source,sh] +[source,console] -------------------------------------------- PUT /_cluster/settings { diff --git a/docs/user/alerting/alerting-troubleshooting.asciidoc b/docs/user/alerting/alerting-troubleshooting.asciidoc index 88b553c9064c1a..a0bcd6dcea2900 100644 --- a/docs/user/alerting/alerting-troubleshooting.asciidoc +++ b/docs/user/alerting/alerting-troubleshooting.asciidoc @@ -55,7 +55,7 @@ Diagnosing these may be difficult - but there may be log messages for error cond === Use the REST APIs There is a rich set of HTTP endpoints to introspect and manage rules and connectors. -One of the HTTP endpoints available for actions is the <>. You can use this to “test” an action. For instance, if you have a server log action created, you can run it via curling the endpoint: +One of the HTTP endpoints available for actions is the run connector API. You can use this to “test” an action. For instance, if you have a server log action created, you can run it via curling the endpoint: [source, txt] -------------------------------------------------- curl -X POST -k \ @@ -219,8 +219,7 @@ the {kib} {alert-features}: If you create a rule in the {observability} or {security-app}, its alerts are not visible in *{stack-manage-app} > {rules-ui}*. You can view them only in the -{kib} app where you created the rule. If you use the -<>, the visibility of the alerts is related to +{kib} app where you created the rule. If you use the create rule API, the visibility of the alerts is related to the `consumer` property. include::troubleshooting/alerting-common-issues.asciidoc[] diff --git a/docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc b/docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc index b7d9ba5168d114..1f5ee611670412 100644 --- a/docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc +++ b/docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc @@ -239,7 +239,7 @@ This query returns the following: <1> Most run durations fall within the first bucket (0 - 1 seconds). <2> A single rule with id `41893910-6bca-11eb-9e0d-85d233e3ee35` took between 30 and 31 seconds to run. -Use the <> to retrieve additional information about rules that take a long time to run. +Use the get rule API to retrieve additional information about rules that take a long time to run. [float] [[rule-cannot-decrypt-api-key]] diff --git a/package.json b/package.json index 3a0c99e9940be3..d8edf6b57e459b 100644 --- a/package.json +++ b/package.json @@ -1773,7 +1773,7 @@ "swagger-ui-express": "^5.0.1", "table": "^6.8.1", "tape": "^5.0.1", - "terser": "^5.31.5", + "terser": "^5.31.6", "terser-webpack-plugin": "^4.2.3", "tough-cookie": "^4.1.4", "tree-kill": "^1.2.2", diff --git a/packages/kbn-esql-ast/src/__tests__/ast_parser.columns.test.ts b/packages/kbn-esql-ast/src/__tests__/ast_parser.columns.test.ts new file mode 100644 index 00000000000000..0a6dbe1f772a2e --- /dev/null +++ b/packages/kbn-esql-ast/src/__tests__/ast_parser.columns.test.ts @@ -0,0 +1,92 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { getAstAndSyntaxErrors as parse } from '../ast_parser'; + +describe('Column Identifier Expressions', () => { + it('can parse un-quoted identifiers', () => { + const text = 'ROW a, b.c'; + const { ast } = parse(text); + + expect(ast).toMatchObject([ + { + type: 'command', + args: [ + { + type: 'column', + parts: ['a'], + }, + { + type: 'column', + parts: ['b', 'c'], + }, + ], + }, + ]); + }); + + it('can parse quoted identifiers', () => { + const text = 'ROW `a`, `b`.c, `d`.`👍`.`123``123`'; + const { ast } = parse(text); + + expect(ast).toMatchObject([ + { + type: 'command', + args: [ + { + type: 'column', + parts: ['a'], + }, + { + type: 'column', + parts: ['b', 'c'], + }, + { + type: 'column', + parts: ['d', '👍', '123`123'], + }, + ], + }, + ]); + }); + + it('can mix quoted and un-quoted identifiers', () => { + const text = 'ROW part1.part2.`part``3️⃣`'; + const { ast } = parse(text); + + expect(ast).toMatchObject([ + { + type: 'command', + args: [ + { + type: 'column', + parts: ['part1', 'part2', 'part`3️⃣'], + }, + ], + }, + ]); + }); + + it('in KEEP command', () => { + const text = 'FROM a | KEEP a.b'; + const { ast } = parse(text); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + args: [ + { + type: 'column', + parts: ['a', 'b'], + }, + ], + }, + ]); + }); +}); diff --git a/packages/kbn-esql-ast/src/ast_helpers.ts b/packages/kbn-esql-ast/src/ast_helpers.ts index 6ebb3fb9c4490b..44f9a2663db171 100644 --- a/packages/kbn-esql-ast/src/ast_helpers.ts +++ b/packages/kbn-esql-ast/src/ast_helpers.ts @@ -11,12 +11,14 @@ */ import { type Token, type ParserRuleContext, type TerminalNode } from 'antlr4'; -import type { - ArithmeticUnaryContext, - DecimalValueContext, - InlineCastContext, - IntegerValueContext, - QualifiedIntegerLiteralContext, +import { + QualifiedNameContext, + type ArithmeticUnaryContext, + type DecimalValueContext, + type InlineCastContext, + type IntegerValueContext, + type QualifiedIntegerLiteralContext, + QualifiedNamePatternContext, } from './antlr/esql_parser'; import { getPosition } from './ast_position_utils'; import { DOUBLE_TICKS_REGEX, SINGLE_BACKTICK, TICKS_REGEX } from './constants'; @@ -39,6 +41,7 @@ import type { FunctionSubtype, ESQLNumericLiteral, } from './types'; +import { parseIdentifier } from './parser/helpers'; export function nonNullable(v: T): v is NonNullable { return v != null; @@ -360,10 +363,13 @@ export function createSource( } export function createColumnStar(ctx: TerminalNode): ESQLColumn { + const text = ctx.getText(); + return { type: 'column', - name: ctx.getText(), - text: ctx.getText(), + name: text, + parts: [text], + text, location: getPosition(ctx.symbol), incomplete: ctx.getText() === '', quoted: false, @@ -371,11 +377,22 @@ export function createColumnStar(ctx: TerminalNode): ESQLColumn { } export function createColumn(ctx: ParserRuleContext): ESQLColumn { + const parts: string[] = []; + if (ctx instanceof QualifiedNamePatternContext) { + parts.push( + ...ctx.identifierPattern_list().map((identifier) => parseIdentifier(identifier.getText())) + ); + } else if (ctx instanceof QualifiedNameContext) { + parts.push(...ctx.identifier_list().map((identifier) => parseIdentifier(identifier.getText()))); + } else { + parts.push(sanitizeIdentifierString(ctx)); + } const text = sanitizeIdentifierString(ctx); const hasQuotes = Boolean(getQuotedText(ctx) || isQuoted(ctx.getText())); return { type: 'column' as const, name: text, + parts, text: ctx.getText(), location: getPosition(ctx.start, ctx.stop), incomplete: Boolean(ctx.exception || text === ''), diff --git a/packages/kbn-esql-ast/src/parser/helpers.ts b/packages/kbn-esql-ast/src/parser/helpers.ts new file mode 100644 index 00000000000000..9aea72a3a20733 --- /dev/null +++ b/packages/kbn-esql-ast/src/parser/helpers.ts @@ -0,0 +1,37 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +export const isQuotedIdentifier = (text: string): boolean => { + const firstChar = text[0]; + const lastChar = text[text.length - 1]; + + return firstChar === '`' && lastChar === '`'; +}; + +export const parseIdentifier = (text: string): string => { + const isQuoted = isQuotedIdentifier(text); + + if (!isQuoted) { + return text; + } + + return text.slice(1, -1).replace(/``/g, '`'); +}; + +export const regexUnquotedIdentifierPattern = /^([a-z\*_\@]{1})[a-z0-9_\*]*$/i; + +export const formatIdentifier = (text: string): string => { + if (regexUnquotedIdentifierPattern.test(text)) { + return text; + } + + return `\`${text.replace(/`/g, '``')}\``; +}; + +export const formatIdentifierParts = (parts: string[]): string => + parts.map(formatIdentifier).join('.'); diff --git a/packages/kbn-esql-ast/src/types.ts b/packages/kbn-esql-ast/src/types.ts index b256556a580622..ae675a375a430a 100644 --- a/packages/kbn-esql-ast/src/types.ts +++ b/packages/kbn-esql-ast/src/types.ts @@ -179,6 +179,22 @@ export interface ESQLSource extends ESQLAstBaseItem { export interface ESQLColumn extends ESQLAstBaseItem { type: 'column'; + + /** + * An identifier can be composed of multiple parts, e.g: part1.part2.`part``3️⃣`. + * This property contains the parsed unquoted parts of the identifier. + * For example: `['part1', 'part2', 'part`3️⃣']`. + */ + parts: string[]; + + /** + * @deprecated + * + * An identifier can be composed of multiple parts, e.g: part1.part2.`part3️⃣` + * + * Each part can be quoted or not quoted independently. A single `quoted` + * property is not enough to represent the identifier. Use `parts` instead. + */ quoted: boolean; } diff --git a/packages/kbn-monaco/src/console/parser.js b/packages/kbn-monaco/src/console/parser.js index e7369444ab85a5..881370f5b9facb 100644 --- a/packages/kbn-monaco/src/console/parser.js +++ b/packages/kbn-monaco/src/console/parser.js @@ -37,24 +37,7 @@ export const createParser = () => { requestStartOffset = at - 1; requests.push({ startOffset: requestStartOffset }); }, - addRequestMethod = function(method) { - const lastRequest = getLastRequest(); - lastRequest.method = method; - requests.push(lastRequest); - requestEndOffset = at - 1; - }, - addRequestUrl = function(url) { - const lastRequest = getLastRequest(); - lastRequest.url = url; - requests.push(lastRequest); - requestEndOffset = at - 1; - }, - addRequestData = function(data) { - const lastRequest = getLastRequest(); - const dataArray = lastRequest.data || []; - dataArray.push(data); - lastRequest.data = dataArray; - requests.push(lastRequest); + updateRequestEnd = function () { requestEndOffset = at - 1; }, addRequestEnd = function() { @@ -409,17 +392,17 @@ export const createParser = () => { request = function () { white(); addRequestStart(); - const parsedMethod = method(); - addRequestMethod(parsedMethod); + method(); + updateRequestEnd(); strictWhite(); - const parsedUrl = url(); - addRequestUrl(parsedUrl); + url(); + updateRequestEnd(); strictWhite(); // advance to one new line newLine(); strictWhite(); if (ch == '{') { - const parsedObject = object(); - addRequestData(parsedObject); + object(); + updateRequestEnd(); } // multi doc request strictWhite(); // advance to one new line @@ -427,8 +410,8 @@ export const createParser = () => { strictWhite(); while (ch == '{') { // another object - const parsedObject = object(); - addRequestData(parsedObject); + object(); + updateRequestEnd(); strictWhite(); newLine(); strictWhite(); diff --git a/packages/kbn-monaco/src/console/parser.test.ts b/packages/kbn-monaco/src/console/parser.test.ts index 417fdd12e2c180..1529d907317cf8 100644 --- a/packages/kbn-monaco/src/console/parser.test.ts +++ b/packages/kbn-monaco/src/console/parser.test.ts @@ -25,9 +25,7 @@ describe('console parser', () => { const { requests, errors } = parser(input) as ConsoleParserResult; expect(requests.length).toBe(1); expect(errors.length).toBe(0); - const { method, url, startOffset, endOffset } = requests[0]; - expect(method).toBe('GET'); - expect(url).toBe('_search'); + const { startOffset, endOffset } = requests[0]; // the start offset of the request is the beginning of the string expect(startOffset).toBe(0); // the end offset of the request is the end of the string @@ -38,6 +36,10 @@ describe('console parser', () => { const input = 'GET _search\nPOST _test_index'; const { requests } = parser(input) as ConsoleParserResult; expect(requests.length).toBe(2); + expect(requests[0].startOffset).toBe(0); + expect(requests[0].endOffset).toBe(11); + expect(requests[1].startOffset).toBe(12); + expect(requests[1].endOffset).toBe(28); }); it('parses a request with a request body', () => { @@ -45,15 +47,8 @@ describe('console parser', () => { 'GET _search\n' + '{\n' + ' "query": {\n' + ' "match_all": {}\n' + ' }\n' + '}'; const { requests } = parser(input) as ConsoleParserResult; expect(requests.length).toBe(1); - const { method, url, data } = requests[0]; - expect(method).toBe('GET'); - expect(url).toBe('_search'); - expect(data).toEqual([ - { - query: { - match_all: {}, - }, - }, - ]); + const { startOffset, endOffset } = requests[0]; + expect(startOffset).toBe(0); + expect(endOffset).toBe(52); }); }); diff --git a/packages/kbn-monaco/src/console/types.ts b/packages/kbn-monaco/src/console/types.ts index 346bd0e6beeebd..4ad7d28ac778b7 100644 --- a/packages/kbn-monaco/src/console/types.ts +++ b/packages/kbn-monaco/src/console/types.ts @@ -14,9 +14,6 @@ export interface ErrorAnnotation { export interface ParsedRequest { startOffset: number; endOffset?: number; - method: string; - url?: string; - data?: Array>; } export interface ConsoleParserResult { errors: ErrorAnnotation[]; diff --git a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts index 5a7cc19ba96780..4e13583d09aefc 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts @@ -11,6 +11,7 @@ import { debounce, range } from 'lodash'; import { ConsoleParsedRequestsProvider, getParsedRequestsProvider, monaco } from '@kbn/monaco'; import { i18n } from '@kbn/i18n'; import { toMountPoint } from '@kbn/react-kibana-mount'; +import { XJson } from '@kbn/es-ui-shared-plugin/public'; import { isQuotaExceededError } from '../../../../services/history'; import { DEFAULT_VARIABLES } from '../../../../../common/constants'; import { getStorage, StorageKeys } from '../../../../services'; @@ -33,13 +34,14 @@ import { replaceRequestVariables, SELECTED_REQUESTS_CLASSNAME, shouldTriggerSuggestions, - stringifyRequest, trackSentRequests, + getRequestFromEditor, } from './utils'; import type { AdjustedParsedRequest } from './types'; import { StorageQuotaError } from '../../../components/storage_quota_error'; import { ContextValue } from '../../../contexts'; +import { containsComments, indentData } from './utils/requests_utils'; const AUTO_INDENTATION_ACTION_LABEL = 'Apply indentations'; const TRIGGER_SUGGESTIONS_ACTION_LABEL = 'Trigger suggestions'; @@ -48,6 +50,7 @@ const DEBOUNCE_HIGHLIGHT_WAIT_MS = 200; const DEBOUNCE_AUTOCOMPLETE_WAIT_MS = 500; const INSPECT_TOKENS_LABEL = 'Inspect tokens'; const INSPECT_TOKENS_HANDLER_ID = 'editor.action.inspectTokens'; +const { collapseLiteralStrings } = XJson; export class MonacoEditorActionsProvider { private parsedRequestsProvider: ConsoleParsedRequestsProvider; @@ -173,12 +176,12 @@ export class MonacoEditorActionsProvider { const selectedRequests: AdjustedParsedRequest[] = []; for (const [index, parsedRequest] of parsedRequests.entries()) { const requestStartLineNumber = getRequestStartLineNumber(parsedRequest, model); - const requestEndLineNumber = getRequestEndLineNumber( + const requestEndLineNumber = getRequestEndLineNumber({ parsedRequest, + nextRequest: parsedRequests.at(index + 1), model, - index, - parsedRequests - ); + startLineNumber, + }); if (requestStartLineNumber > endLineNumber) { // request is past the selection, no need to check further requests break; @@ -198,13 +201,31 @@ export class MonacoEditorActionsProvider { } public async getRequests() { + const model = this.editor.getModel(); + if (!model) { + return []; + } + const parsedRequests = await this.getSelectedParsedRequests(); - const stringifiedRequests = parsedRequests.map((parsedRequest) => - stringifyRequest(parsedRequest) - ); + const stringifiedRequests = parsedRequests.map((parsedRequest) => { + const { startLineNumber, endLineNumber } = parsedRequest; + const requestTextFromEditor = getRequestFromEditor(model, startLineNumber, endLineNumber); + if (requestTextFromEditor && requestTextFromEditor.data.length > 0) { + requestTextFromEditor.data = requestTextFromEditor.data.map((dataString) => { + if (containsComments(dataString)) { + // parse and stringify to remove comments + dataString = indentData(dataString); + } + return collapseLiteralStrings(dataString); + }); + } + return requestTextFromEditor; + }); // get variables values const variables = getStorage().get(StorageKeys.VARIABLES, DEFAULT_VARIABLES); - return stringifiedRequests.map((request) => replaceRequestVariables(request, variables)); + return stringifiedRequests + .filter(Boolean) + .map((request) => replaceRequestVariables(request!, variables)); } public async getCurl(elasticsearchBaseUrl: string): Promise { @@ -388,12 +409,6 @@ export class MonacoEditorActionsProvider { return null; } - // if the current request doesn't have a method, the request is not valid - // and shouldn't have an autocomplete type - if (!currentRequest.method) { - return null; - } - // if not on the 1st line of the request, suggest request body return AutocompleteType.BODY; } diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.ts index bbfa33d84e70ee..5c6a9885c36274 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.ts @@ -58,7 +58,7 @@ export const getDocumentationLinkFromAutocomplete = ( * Helper function that filters out suggestions without a name. */ const filterTermsWithoutName = (terms: ResultTerm[]): ResultTerm[] => - terms.filter((term) => term.name !== undefined); + terms.filter((term) => term.name !== undefined && term.name !== ''); /* * This function returns an array of completion items for the request method diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/constants.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/constants.ts index 0f4664be489947..7e1ebbc85a50a0 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/constants.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/constants.ts @@ -40,6 +40,10 @@ export const END_OF_URL_TOKEN = '__url_path_end__'; * In this case autocomplete suggestions should be triggered for an url. */ export const methodWhitespaceRegex = /^\s*(GET|POST|PUT|PATCH|DELETE)\s+$/i; +/* + * This regex matches a string that starts with a method (optional whitespace before the method) + */ +export const startsWithMethodRegex = /^\s*(GET|POST|PUT|PATCH|DELETE)/i; /* * This regex matches a string that has * a method and some parts of an url ending with a slash, a question mark or an equals sign, diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/index.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/index.ts index 0997aa682b6300..bc402875633a0d 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/index.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/index.ts @@ -18,11 +18,11 @@ export { export { getRequestStartLineNumber, getRequestEndLineNumber, - stringifyRequest, replaceRequestVariables, getCurlRequest, trackSentRequests, getAutoIndentedRequests, + getRequestFromEditor, } from './requests_utils'; export { getDocumentationLinkFromAutocomplete, diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/requests_utils.test.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/requests_utils.test.ts index 8704ae9e94eec0..f028bfde480868 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/requests_utils.test.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/requests_utils.test.ts @@ -6,14 +6,16 @@ * Side Public License, v 1. */ +import { monaco, ParsedRequest } from '@kbn/monaco'; +import type { MetricsTracker } from '../../../../../types'; import { getAutoIndentedRequests, getCurlRequest, + getRequestEndLineNumber, replaceRequestVariables, - stringifyRequest, trackSentRequests, + getRequestFromEditor, } from './requests_utils'; -import { MetricsTracker } from '../../../../../types'; describe('requests_utils', () => { const dataObjects = [ @@ -26,35 +28,23 @@ describe('requests_utils', () => { test: 'test', }, ]; - describe('stringifyRequest', () => { - const request = { - startOffset: 0, - endOffset: 11, - method: 'get', - url: '_search some_text', - }; - it('calls the "removeTrailingWhitespaces" on the url', () => { - const stringifiedRequest = stringifyRequest(request); - expect(stringifiedRequest.url).toBe('_search'); - }); - - it('normalizes the method to upper case', () => { - const stringifiedRequest = stringifyRequest(request); - expect(stringifiedRequest.method).toBe('GET'); - }); - it('stringifies the request body', () => { - const result = stringifyRequest({ ...request, data: [dataObjects[0]] }); - expect(result.data.length).toBe(1); - expect(result.data[0]).toBe(JSON.stringify(dataObjects[0], null, 2)); - }); - - it('works for several request bodies', () => { - const result = stringifyRequest({ ...request, data: dataObjects }); - expect(result.data.length).toBe(2); - expect(result.data[0]).toBe(JSON.stringify(dataObjects[0], null, 2)); - expect(result.data[1]).toBe(JSON.stringify(dataObjects[1], null, 2)); - }); - }); + const inlineData = '{"query":"test"}'; + const multiLineData = '{\n "query": "test"\n}'; + const invalidData = '{\n "query":\n {'; + const getMockModel = (content: string[]) => { + return { + getLineContent: (lineNumber: number) => content[lineNumber - 1], + getValueInRange: ({ + startLineNumber, + endLineNumber, + }: { + startLineNumber: number; + endLineNumber: number; + }) => content.slice(startLineNumber - 1, endLineNumber).join('\n'), + getLineMaxColumn: (lineNumber: number) => content[lineNumber - 1].length, + getLineCount: () => content.length, + } as unknown as monaco.editor.ITextModel; + }; describe('replaceRequestVariables', () => { const variables = [ @@ -213,9 +203,6 @@ describe('requests_utils', () => { ]; const TEST_REQUEST_1 = { - method: 'GET', - url: '_search', - data: [{ query: { match_all: {} } }], // Offsets are with respect to the sample editor text startLineNumber: 2, endLineNumber: 7, @@ -224,9 +211,6 @@ describe('requests_utils', () => { }; const TEST_REQUEST_2 = { - method: 'GET', - url: '_all', - data: [], // Offsets are with respect to the sample editor text startLineNumber: 10, endLineNumber: 10, @@ -235,10 +219,6 @@ describe('requests_utils', () => { }; const TEST_REQUEST_3 = { - method: 'POST', - url: '/_bulk', - // Multi-data - data: [{ index: { _index: 'books' } }, { name: '1984' }, { name: 'Atomic habits' }], // Offsets are with respect to the sample editor text startLineNumber: 15, endLineNumber: 23, @@ -247,11 +227,8 @@ describe('requests_utils', () => { }; const TEST_REQUEST_4 = { - method: 'GET', - url: '_search', - data: [{ query: { match_all: {} } }], // Offsets are with respect to the sample editor text - startLineNumber: 24, + startLineNumber: 25, endLineNumber: 30, startOffset: 1, endOffset: 36, @@ -353,17 +330,131 @@ describe('requests_utils', () => { expect(formattedData).toBe(expectedResultLines.join('\n')); }); - it('does not auto-indent a request with comments', () => { - const requestText = sampleEditorTextLines - .slice(TEST_REQUEST_4.startLineNumber - 1, TEST_REQUEST_4.endLineNumber) + it(`auto-indents method line but doesn't auto-indent data with comments`, () => { + const methodLine = sampleEditorTextLines[TEST_REQUEST_4.startLineNumber - 1]; + const dataText = sampleEditorTextLines + .slice(TEST_REQUEST_4.startLineNumber, TEST_REQUEST_4.endLineNumber) .join('\n'); const formattedData = getAutoIndentedRequests( [TEST_REQUEST_4], - requestText, + `${methodLine}\n${dataText}`, sampleEditorTextLines.join('\n') ); - expect(formattedData).toBe(requestText); + expect(formattedData).toBe(`GET _search // test comment\n${dataText}`); + }); + }); + + describe('getRequestEndLineNumber', () => { + const parsedRequest: ParsedRequest = { + startOffset: 1, + }; + it('detects the end of the request when there is a line that starts with a method (next not parsed request)', () => { + /* + * Mocking the model to return these 6 lines of text + * 1. GET /_search + * 2. { + * 3. empty + * 4. empty + * 5. POST _search + * 6. empty + */ + const content = ['GET /_search', '{', '', '', 'POST _search', '']; + const model = { + ...getMockModel(content), + getPositionAt: () => ({ lineNumber: 1 }), + } as unknown as monaco.editor.ITextModel; + + const result = getRequestEndLineNumber({ + parsedRequest, + model, + startLineNumber: 1, + }); + expect(result).toEqual(2); + }); + + it('detects the end of the request when the text ends', () => { + /* + * Mocking the model to return these 4 lines of text + * 1. GET /_search + * 2. { + * 3. { + * 4. empty + */ + const content = ['GET _search', '{', ' {', '']; + const model = { + ...getMockModel(content), + getPositionAt: () => ({ lineNumber: 1 }), + } as unknown as monaco.editor.ITextModel; + + const result = getRequestEndLineNumber({ + parsedRequest, + model, + startLineNumber: 1, + }); + expect(result).toEqual(3); + }); + }); + + describe('getRequestFromEditor', () => { + it('cleans up any text following the url', () => { + const content = ['GET _search // inline comment']; + const model = getMockModel(content); + const request = getRequestFromEditor(model, 1, 1); + expect(request).toEqual({ method: 'GET', url: '_search', data: [] }); + }); + + it(`doesn't incorrectly removes parts of url params that include whitespaces`, () => { + const content = ['GET _search?query="test test"']; + const model = getMockModel(content); + const request = getRequestFromEditor(model, 1, 1); + expect(request).toEqual({ method: 'GET', url: '_search?query="test test"', data: [] }); + }); + + it(`normalizes method to upper case`, () => { + const content = ['get _search']; + const model = getMockModel(content); + const request = getRequestFromEditor(model, 1, 1); + expect(request).toEqual({ method: 'GET', url: '_search', data: [] }); + }); + + it('correctly includes the request body', () => { + const content = ['GET _search', '{', ' "query": {}', '}']; + const model = getMockModel(content); + const request = getRequestFromEditor(model, 1, 4); + expect(request).toEqual({ method: 'GET', url: '_search', data: ['{\n "query": {}\n}'] }); + }); + + it('works for several request bodies', () => { + const content = ['GET _search', '{', ' "query": {}', '}', '{', ' "query": {}', '}']; + const model = getMockModel(content); + const request = getRequestFromEditor(model, 1, 7); + expect(request).toEqual({ + method: 'GET', + url: '_search', + data: ['{\n "query": {}\n}', '{\n "query": {}\n}'], + }); + }); + + it('splits several json objects', () => { + const content = ['GET _search', inlineData, ...multiLineData.split('\n'), inlineData]; + const model = getMockModel(content); + const request = getRequestFromEditor(model, 1, 6); + expect(request).toEqual({ + method: 'GET', + url: '_search', + data: [inlineData, multiLineData, inlineData], + }); + }); + it('works for invalid json objects', () => { + const content = ['GET _search', inlineData, ...invalidData.split('\n')]; + const model = getMockModel(content); + const request = getRequestFromEditor(model, 1, 5); + expect(request).toEqual({ + method: 'GET', + url: '_search', + data: [inlineData, invalidData], + }); }); }); }); diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/requests_utils.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/requests_utils.ts index 8f791751bba678..b6f5921e920a4e 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/requests_utils.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/requests_utils.ts @@ -7,26 +7,17 @@ */ import { monaco, ParsedRequest } from '@kbn/monaco'; +import { parse } from 'hjson'; import { constructUrl } from '../../../../../lib/es'; -import { MetricsTracker } from '../../../../../types'; +import type { MetricsTracker } from '../../../../../types'; import type { DevToolsVariable } from '../../../../components'; -import type { EditorRequest } from '../types'; -import { urlVariableTemplateRegex, dataVariableTemplateRegex } from './constants'; -import { removeTrailingWhitespaces } from './tokens_utils'; -import { AdjustedParsedRequest } from '../types'; - -/* - * This function stringifies and normalizes the parsed request: - * - the method is converted to upper case - * - any trailing comments are removed from the url - * - the request body is stringified from an object using JSON.stringify - */ -export const stringifyRequest = (parsedRequest: ParsedRequest): EditorRequest => { - const url = parsedRequest.url ? removeTrailingWhitespaces(parsedRequest.url) : ''; - const method = parsedRequest.method?.toUpperCase() ?? ''; - const data = parsedRequest.data?.map((parsedData) => JSON.stringify(parsedData, null, 2)); - return { url, method, data: data ?? [] }; -}; +import type { EditorRequest, AdjustedParsedRequest } from '../types'; +import { + urlVariableTemplateRegex, + dataVariableTemplateRegex, + startsWithMethodRegex, +} from './constants'; +import { parseLine } from './tokens_utils'; /* * This function replaces any variables with its values stored in localStorage. @@ -52,9 +43,13 @@ export const getCurlRequest = ( ): string => { const curlUrl = constructUrl(elasticsearchBaseUrl, url); let curlRequest = `curl -X${method} "${curlUrl}" -H "kbn-xsrf: reporting"`; - if (data.length > 0) { + if (data && data.length) { + const joinedData = data.join('\n'); + curlRequest += ` -H "Content-Type: application/json" -d'\n`; - curlRequest += data.join('\n'); + + // We escape single quoted strings that are wrapped in single quoted strings + curlRequest += joinedData.replace(/'/g, "'\\''"); curlRequest += "'"; } return curlRequest; @@ -88,25 +83,42 @@ export const getRequestStartLineNumber = ( * If there is no end offset (the parser was not able to parse this request completely), * then the last non-empty line is returned or the line before the next request. */ -export const getRequestEndLineNumber = ( - parsedRequest: ParsedRequest, - model: monaco.editor.ITextModel, - index: number, - parsedRequests: ParsedRequest[] -): number => { +export const getRequestEndLineNumber = ({ + parsedRequest, + nextRequest, + model, + startLineNumber, +}: { + parsedRequest: ParsedRequest; + nextRequest?: ParsedRequest; + model: monaco.editor.ITextModel; + startLineNumber: number; +}): number => { let endLineNumber: number; if (parsedRequest.endOffset) { // if the parser set an end offset for this request, then find the line number for it endLineNumber = model.getPositionAt(parsedRequest.endOffset).lineNumber; } else { // if no end offset, try to find the line before the next request starts - const nextRequest = parsedRequests.at(index + 1); if (nextRequest) { const nextRequestStartLine = model.getPositionAt(nextRequest.startOffset).lineNumber; - endLineNumber = nextRequestStartLine - 1; + endLineNumber = + nextRequestStartLine > startLineNumber ? nextRequestStartLine - 1 : startLineNumber; } else { - // if there is no next request, take the last line of the model - endLineNumber = model.getLineCount(); + // if there is no next request, find the end of the text or the line that starts with a method + let nextLineNumber = model.getPositionAt(parsedRequest.startOffset).lineNumber + 1; + let nextLineContent: string; + while (nextLineNumber <= model.getLineCount()) { + nextLineContent = model.getLineContent(nextLineNumber).trim(); + if (nextLineContent.match(startsWithMethodRegex)) { + // found a line that starts with a method, stop iterating + break; + } + nextLineNumber++; + } + // nextLineNumber is now either the line with a method or 1 line after the end of the text + // set the end line for this request to the line before nextLineNumber + endLineNumber = nextLineNumber > startLineNumber ? nextLineNumber - 1 : startLineNumber; } } // if the end line is empty, go up to find the first non-empty line @@ -118,44 +130,6 @@ export const getRequestEndLineNumber = ( return endLineNumber; }; -const isJsonString = (str: string) => { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; -}; - -/* - * Internal helpers - */ -const replaceVariables = ( - text: string, - variables: DevToolsVariable[], - isDataVariable: boolean -): string => { - const variableRegex = isDataVariable ? dataVariableTemplateRegex : urlVariableTemplateRegex; - if (variableRegex.test(text)) { - text = text.replaceAll(variableRegex, (match, key) => { - const variable = variables.find(({ name }) => name === key); - const value = variable?.value; - - if (isDataVariable && value) { - // If the variable value is an object, add it as it is. Otherwise, surround it with quotes. - return isJsonString(value) ? value : `"${value}"`; - } - - return value ?? match; - }); - } - return text; -}; - -const containsComments = (text: string) => { - return text.indexOf('//') >= 0 || text.indexOf('/*') >= 0; -}; - /** * This function takes a string containing unformatted Console requests and * returns a text in which the requests are auto-indented. @@ -184,19 +158,19 @@ export const getAutoIndentedRequests = ( ) { // Start of a request const requestLines = allTextLines.slice(request.startLineNumber - 1, request.endLineNumber); - - if (requestLines.some((line) => containsComments(line))) { - // If request has comments, add it as it is - without formatting - // TODO: Format requests with comments - formattedTextLines.push(...requestLines); + const firstLine = cleanUpWhitespaces(requestLines[0]); + formattedTextLines.push(firstLine); + const dataLines = requestLines.slice(1); + if (dataLines.some((line) => containsComments(line))) { + // If data has comments, add it as it is - without formatting + // TODO: Format requests with comments https://github.com/elastic/kibana/issues/182138 + formattedTextLines.push(...dataLines); } else { - // If no comments, add stringified parsed request - const stringifiedRequest = stringifyRequest(request); - const firstLine = stringifiedRequest.method + ' ' + stringifiedRequest.url; - formattedTextLines.push(firstLine); - - if (stringifiedRequest.data && stringifiedRequest.data.length > 0) { - formattedTextLines.push(...stringifiedRequest.data); + // If no comments, indent data + if (requestLines.length > 1) { + const dataString = dataLines.join('\n'); + const dataJsons = splitDataIntoJsonObjects(dataString); + formattedTextLines.push(...dataJsons.map(indentData)); } } @@ -205,10 +179,116 @@ export const getAutoIndentedRequests = ( } else { // Current line is a comment or whitespaces // Trim white spaces and add it to the formatted text - formattedTextLines.push(selectedTextLines[currentLineIndex].trim()); + formattedTextLines.push(cleanUpWhitespaces(selectedTextLines[currentLineIndex])); currentLineIndex++; } } return formattedTextLines.join('\n'); }; + +/* + * This function extracts a normalized method and url from the editor and + * the "raw" text of the request body without any changes to it. The only normalization + * for request body is to split several json objects into an array of strings. + */ +export const getRequestFromEditor = ( + model: monaco.editor.ITextModel, + startLineNumber: number, + endLineNumber: number +): EditorRequest | null => { + const methodUrlLine = model.getLineContent(startLineNumber).trim(); + if (!methodUrlLine) { + return null; + } + const { method, url } = parseLine(methodUrlLine, false); + if (!method || !url) { + return null; + } + const upperCaseMethod = method.toUpperCase(); + + if (endLineNumber <= startLineNumber) { + return { method: upperCaseMethod, url, data: [] }; + } + const dataString = model + .getValueInRange({ + startLineNumber: startLineNumber + 1, + startColumn: 1, + endLineNumber, + endColumn: model.getLineMaxColumn(endLineNumber), + }) + .trim(); + const data = splitDataIntoJsonObjects(dataString); + + return { method: upperCaseMethod, url, data }; +}; + +export const containsComments = (text: string) => { + return text.indexOf('//') >= 0 || text.indexOf('/*') >= 0; +}; + +export const indentData = (dataString: string): string => { + try { + const parsedData = parse(dataString); + + return JSON.stringify(parsedData, null, 2); + } catch (e) { + return dataString; + } +}; + +// ---------------------------------- Internal helpers ---------------------------------- + +const isJsonString = (str: string) => { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; +}; + +const replaceVariables = ( + text: string, + variables: DevToolsVariable[], + isDataVariable: boolean +): string => { + const variableRegex = isDataVariable ? dataVariableTemplateRegex : urlVariableTemplateRegex; + if (variableRegex.test(text)) { + text = text.replaceAll(variableRegex, (match, key) => { + const variable = variables.find(({ name }) => name === key); + const value = variable?.value; + + if (isDataVariable && value) { + // If the variable value is an object, add it as it is. Otherwise, surround it with quotes. + return isJsonString(value) ? value : `"${value}"`; + } + + return value ?? match; + }); + } + return text; +}; + +const splitDataIntoJsonObjects = (dataString: string): string[] => { + const jsonSplitRegex = /}\s*{/; + if (dataString.match(jsonSplitRegex)) { + return dataString.split(jsonSplitRegex).map((part, index, parts) => { + let restoredBracketsString = part; + // add an opening bracket to all parts except the 1st + if (index > 0) { + restoredBracketsString = `{${restoredBracketsString}`; + } + // add a closing bracket to all parts except the last + if (index < parts.length - 1) { + restoredBracketsString = `${restoredBracketsString}}`; + } + return restoredBracketsString; + }); + } + return [dataString]; +}; + +const cleanUpWhitespaces = (line: string): string => { + return line.trim().replaceAll(/\s+/g, ' '); +}; diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.test.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.test.ts index 702b9a589e6621..4e7a383ceebf3a 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.test.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { parseBody, removeTrailingWhitespaces, parseUrl } from './tokens_utils'; +import { parseBody, removeTrailingWhitespaces, parseUrl, parseLine } from './tokens_utils'; describe('tokens_utils', () => { describe('removeTrailingWhitespaces', () => { @@ -32,6 +32,53 @@ describe('tokens_utils', () => { }); }); + describe('parseLine', () => { + it('works with a comment', () => { + const { method, url } = parseLine('GET _search // a comment'); + expect(method).toBe('GET'); + expect(url).toBe('_search'); + }); + it('works with a url param', () => { + const { method, url, urlPathTokens, urlParamsTokens } = parseLine( + 'GET _search?query="test1 test2 test3" // comment' + ); + expect(method).toBe('GET'); + expect(url).toBe('_search?query="test1 test2 test3"'); + expect(urlPathTokens).toEqual(['_search']); + expect(urlParamsTokens[0]).toEqual(['query', '"test1 test2 test3"']); + }); + it('works with multiple whitespaces', () => { + const { method, url, urlPathTokens, urlParamsTokens } = parseLine( + ' GET _search?query="test1 test2 test3" // comment' + ); + expect(method).toBe('GET'); + expect(url).toBe('_search?query="test1 test2 test3"'); + expect(urlPathTokens).toEqual(['_search']); + expect(urlParamsTokens[0]).toEqual(['query', '"test1 test2 test3"']); + }); + it('normalizes the method to upper case', () => { + const { method, url, urlPathTokens, urlParamsTokens } = parseLine('Get _'); + expect(method).toBe('GET'); + expect(url).toBe('_'); + expect(urlPathTokens).toEqual(['_']); + expect(urlParamsTokens).toEqual([]); + }); + it('correctly parses the line when the url is empty, no whitespace', () => { + const { method, url, urlPathTokens, urlParamsTokens } = parseLine('GET'); + expect(method).toBe('GET'); + expect(url).toBe(''); + expect(urlPathTokens).toEqual([]); + expect(urlParamsTokens).toEqual([]); + }); + it('correctly parses the line when the url is empty, with whitespace', () => { + const { method, url, urlPathTokens, urlParamsTokens } = parseLine('GET '); + expect(method).toBe('GET'); + expect(url).toBe(''); + expect(urlPathTokens).toEqual([]); + expect(urlParamsTokens).toEqual([]); + }); + }); + describe('parseBody', () => { const testCases: Array<{ value: string; tokens: string[] }> = [ { diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.ts index 2615bd2c45d74e..8e9c723c8f7a2a 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.ts @@ -19,18 +19,25 @@ import { /* * This function parses a line with the method and url. * The url is parsed into path and params, each parsed into tokens. - * Returns method, urlPathTokens and urlParamsTokens which are arrays of strings. + * Returns method, url, urlPathTokens and urlParamsTokens which are arrays of strings. */ -export const parseLine = (line: string): ParsedLineTokens => { - // try to parse into method and url (split on whitespace) - const parts = line.split(whitespacesRegex); +export const parseLine = (line: string, parseUrlIntoTokens: boolean = true): ParsedLineTokens => { + line = line.trim(); + const firstWhitespaceIndex = line.indexOf(' '); + if (firstWhitespaceIndex < 0) { + // there is no url, only method + return { method: line, url: '', urlPathTokens: [], urlParamsTokens: [] }; + } // 1st part is the method - const method = parts[0].toUpperCase(); + const method = line.slice(0, firstWhitespaceIndex).trim().toUpperCase(); // 2nd part is the url - const url = parts[1]; - // try to parse into url path and url params (split on question mark) - const { urlPathTokens, urlParamsTokens } = parseUrl(url); - return { method, urlPathTokens, urlParamsTokens }; + const url = removeTrailingWhitespaces(line.slice(firstWhitespaceIndex).trim()); + if (parseUrlIntoTokens) { + // try to parse into url path and url params (split on question mark) + const { urlPathTokens, urlParamsTokens } = parseUrl(url); + return { method, url, urlPathTokens, urlParamsTokens }; + } + return { method, url, urlPathTokens: [], urlParamsTokens: [] }; }; /* @@ -444,6 +451,7 @@ export const containsUrlParams = (lineContent: string): boolean => { */ interface ParsedLineTokens { method: string; + url: string; urlPathTokens: string[]; urlParamsTokens: string[][]; } diff --git a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx index 735eae1fa9039d..1c2e7f36899295 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx @@ -78,6 +78,17 @@ export const DiscoverMainContent = ({ trackUiMetric(METRIC_TYPE.CLICK, DOCUMENTS_VIEW_CLICK); } } + + return new Promise((resolve, reject) => { + // return a promise to report when the view mode has been updated + stateContainer.appState.subscribe((state) => { + if (state.viewMode === mode) { + resolve(mode); + } else { + reject(mode); + } + }); + }); }, [trackUiMetric, stateContainer] ); diff --git a/src/plugins/discover/public/application/main/components/pattern_analysis/pattern_analysis_tab.tsx b/src/plugins/discover/public/application/main/components/pattern_analysis/pattern_analysis_tab.tsx index db02134e0169fb..acb00fd027f575 100644 --- a/src/plugins/discover/public/application/main/components/pattern_analysis/pattern_analysis_tab.tsx +++ b/src/plugins/discover/public/application/main/components/pattern_analysis/pattern_analysis_tab.tsx @@ -7,24 +7,16 @@ */ import React, { memo, type FC } from 'react'; -import { useQuerySubscriber } from '@kbn/unified-field-list/src/hooks/use_query_subscriber'; import { useSavedSearch } from '../../state_management/discover_state_provider'; import { PatternAnalysisTable, type PatternAnalysisTableProps } from './pattern_analysis_table'; -import { useDiscoverServices } from '../../../../hooks/use_discover_services'; export const PatternAnalysisTab: FC> = memo( (props) => { - const services = useDiscoverServices(); - const querySubscriberResult = useQuerySubscriber({ - data: services.data, - }); const savedSearch = useSavedSearch(); return ( { const patternAnalysisComponentProps: LogCategorizationEmbeddableProps = useMemo( () => ({ - input: Object.assign( - {}, - pick(props, ['dataView', 'savedSearch', 'query', 'filters', 'switchToDocumentView']), - { lastReloadRequestTime } - ), + input: Object.assign({}, pick(props, ['dataView', 'savedSearch', 'switchToDocumentView']), { + lastReloadRequestTime, + }), renderViewModeToggle: props.renderViewModeToggle, }), [lastReloadRequestTime, props] diff --git a/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx b/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx index 28eeb9f3661ffa..d5c00d812c7ca3 100644 --- a/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx +++ b/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx @@ -31,7 +31,7 @@ export const DocumentViewModeToggle = ({ isEsqlMode: boolean; prepend?: ReactElement; stateContainer: DiscoverStateContainer; - setDiscoverViewMode: (viewMode: VIEW_MODE) => void; + setDiscoverViewMode: (viewMode: VIEW_MODE) => Promise; patternCount?: number; dataView: DataView; }) => { @@ -86,8 +86,7 @@ export const DocumentViewModeToggle = ({ } }, [showPatternAnalysisTab, viewMode, setDiscoverViewMode]); - const includesNormalTabsStyle = - viewMode === VIEW_MODE.AGGREGATED_LEVEL || viewMode === VIEW_MODE.PATTERN_LEVEL || isLegacy; + const includesNormalTabsStyle = viewMode === VIEW_MODE.AGGREGATED_LEVEL || isLegacy; const containerPadding = includesNormalTabsStyle ? euiTheme.size.s : 0; const containerCss = css` diff --git a/test/functional/apps/console/monaco/_console.ts b/test/functional/apps/console/monaco/_console.ts index 1c6afc39c50467..b48ba755295798 100644 --- a/test/functional/apps/console/monaco/_console.ts +++ b/test/functional/apps/console/monaco/_console.ts @@ -14,7 +14,6 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const log = getService('log'); - const toasts = getService('toasts'); const browser = getService('browser'); const PageObjects = getPageObjects(['common', 'console', 'header']); const security = getService('security'); @@ -58,12 +57,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(initialSize.width).to.be.greaterThan(afterSize.width); }); - it('should not send request with unsupported HTTP verbs', async () => { + it('should return statusCode 400 to unsupported HTTP verbs', async () => { + const expectedResponseContains = '"statusCode": 400'; await PageObjects.console.monaco.clearEditorText(); await PageObjects.console.monaco.enterText('OPTIONS /'); await PageObjects.console.clickPlay(); await retry.try(async () => { - expect(await toasts.getCount()).to.equal(1); + const actualResponse = await PageObjects.console.monaco.getOutputText(); + log.debug(actualResponse); + expect(actualResponse).to.contain(expectedResponseContains); + + expect(await PageObjects.console.hasSuccessBadge()).to.be(false); }); }); diff --git a/test/functional/apps/console/monaco/_context_menu.ts b/test/functional/apps/console/monaco/_context_menu.ts index 0fbcb123937db5..1e95e74a851b1c 100644 --- a/test/functional/apps/console/monaco/_context_menu.ts +++ b/test/functional/apps/console/monaco/_context_menu.ts @@ -133,15 +133,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.switchTab(0); }); - // not implemented yet for monaco https://github.com/elastic/kibana/issues/185891 - it.skip('should toggle auto indent when auto indent button is clicked', async () => { - await PageObjects.console.clearTextArea(); - await PageObjects.console.enterRequest('GET _search\n{"query": {"match_all": {}}}'); + it('should auto indent when auto indent button is clicked', async () => { + await PageObjects.console.monaco.clearEditorText(); + await PageObjects.console.monaco.enterText('GET _search\n{"query": {"match_all": {}}}'); + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickAutoIndentButton(); + // Retry until the request is auto indented + await retry.try(async () => { + const request = await PageObjects.console.monaco.getEditorText(); + expect(request).to.be.eql('GET _search\n{\n "query": {\n "match_all": {}\n }\n}'); + }); + }); + + // not implemented for monaco yet https://github.com/elastic/kibana/issues/185891 + it.skip('should collapse the request when auto indent button is clicked again', async () => { + await PageObjects.console.monaco.clearEditorText(); + await PageObjects.console.monaco.enterText('GET _search\n{"query": {"match_all": {}}}'); await PageObjects.console.clickContextMenu(); await PageObjects.console.clickAutoIndentButton(); // Retry until the request is auto indented await retry.try(async () => { - const request = await PageObjects.console.getRequest(); + const request = await PageObjects.console.monaco.getEditorText(); expect(request).to.be.eql('GET _search\n{\n "query": {\n "match_all": {}\n }\n}'); }); @@ -150,7 +162,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.console.clickAutoIndentButton(); // Retry until the request is condensed await retry.try(async () => { - const request = await PageObjects.console.getRequest(); + const request = await PageObjects.console.monaco.getEditorText(); expect(request).to.be.eql('GET _search\n{"query":{"match_all":{}}}'); }); }); diff --git a/test/functional/apps/console/monaco/_misc_console_behavior.ts b/test/functional/apps/console/monaco/_misc_console_behavior.ts index f7ad2957c8411f..f9b93872740adf 100644 --- a/test/functional/apps/console/monaco/_misc_console_behavior.ts +++ b/test/functional/apps/console/monaco/_misc_console_behavior.ts @@ -147,5 +147,34 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); }); + + describe('invalid requests', () => { + const invalidRequestText = 'GET _search\n{"query": {"match_all": {'; + it(`should not delete any text if indentations applied to an invalid request`, async () => { + await PageObjects.console.monaco.clearEditorText(); + await PageObjects.console.monaco.enterText(invalidRequestText); + await PageObjects.console.monaco.selectCurrentRequest(); + await PageObjects.console.monaco.pressCtrlI(); + // Sleep for a bit and then check that the text has not changed + await PageObjects.common.sleep(1000); + await retry.try(async () => { + const request = await PageObjects.console.monaco.getEditorText(); + expect(request).to.be.eql(invalidRequestText); + }); + }); + + it(`should include an invalid json when sending a request`, async () => { + await PageObjects.console.monaco.clearEditorText(); + await PageObjects.console.monaco.enterText(invalidRequestText); + await PageObjects.console.monaco.selectCurrentRequest(); + await PageObjects.console.monaco.pressCtrlEnter(); + + await retry.try(async () => { + const actualResponse = await PageObjects.console.monaco.getOutputText(); + expect(actualResponse).to.contain('parsing_exception'); + expect(await PageObjects.console.hasSuccessBadge()).to.be(false); + }); + }); + }); }); } diff --git a/x-pack/packages/ml/aiops_log_pattern_analysis/constants.ts b/x-pack/packages/ml/aiops_log_pattern_analysis/constants.ts new file mode 100644 index 00000000000000..e88944a83b8bbe --- /dev/null +++ b/x-pack/packages/ml/aiops_log_pattern_analysis/constants.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const EMBEDDABLE_PATTERN_ANALYSIS_TYPE = 'aiopsPatternAnalysisEmbeddable' as const; + +export const PATTERN_ANALYSIS_DATA_VIEW_REF_NAME = 'aiopsPatternAnalysisEmbeddableDataViewId'; diff --git a/x-pack/packages/ml/aiops_log_pattern_analysis/embeddable.ts b/x-pack/packages/ml/aiops_log_pattern_analysis/embeddable.ts index 57c6b144c15d09..f236c24cfe6a2b 100644 --- a/x-pack/packages/ml/aiops_log_pattern_analysis/embeddable.ts +++ b/x-pack/packages/ml/aiops_log_pattern_analysis/embeddable.ts @@ -5,16 +5,13 @@ * 2.0. */ -import type { Query, AggregateQuery, Filter } from '@kbn/es-query'; -import type { SavedSearch } from '@kbn/saved-search-plugin/public'; +import type { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; -export interface EmbeddablePatternAnalysisInput { +export interface EmbeddablePatternAnalysisInput { dataView: DataView; savedSearch?: SavedSearch | null; - query?: T; - filters?: Filter[]; embeddingOrigin?: string; - switchToDocumentView?: () => void; + switchToDocumentView?: () => Promise; lastReloadRequestTime?: number; } diff --git a/x-pack/packages/ml/aiops_log_pattern_analysis/tsconfig.json b/x-pack/packages/ml/aiops_log_pattern_analysis/tsconfig.json index afc6d3b1e0631e..fb51a4d8c1b308 100644 --- a/x-pack/packages/ml/aiops_log_pattern_analysis/tsconfig.json +++ b/x-pack/packages/ml/aiops_log_pattern_analysis/tsconfig.json @@ -20,7 +20,6 @@ "@kbn/config-schema", "@kbn/i18n", "@kbn/ml-runtime-field-utils", - "@kbn/es-query", "@kbn/saved-search-plugin", "@kbn/data-views-plugin", "@kbn/ml-is-populated-object", diff --git a/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts b/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts index 4d065fb062cb55..a453360e6b94f8 100644 --- a/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts +++ b/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts @@ -278,7 +278,7 @@ export type InferenceServiceSettings = export type InferenceAPIConfigResponse = { // Refers to a deployment id - model_id: string; + inference_id: string; task_type: 'sparse_embedding' | 'text_embedding'; task_settings: { model?: string; diff --git a/x-pack/plugins/actions/server/action_type_registry.test.ts b/x-pack/plugins/actions/server/action_type_registry.test.ts index 76f9ae838de45a..88ae6c36785520 100644 --- a/x-pack/plugins/actions/server/action_type_registry.test.ts +++ b/x-pack/plugins/actions/server/action_type_registry.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { TaskCost } from '@kbn/task-manager-plugin/server'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { schema } from '@kbn/config-schema'; import { ActionTypeRegistry, ActionTypeRegistryOpts } from './action_type_registry'; @@ -88,6 +89,7 @@ describe('actionTypeRegistry', () => { 'actions:my-action-type': { createTaskRunner: expect.any(Function), maxAttempts: 3, + cost: TaskCost.Tiny, title: 'My action type', }, }, diff --git a/x-pack/plugins/actions/server/action_type_registry.ts b/x-pack/plugins/actions/server/action_type_registry.ts index 615b0b43e9d6ce..a5626450d9346e 100644 --- a/x-pack/plugins/actions/server/action_type_registry.ts +++ b/x-pack/plugins/actions/server/action_type_registry.ts @@ -7,7 +7,7 @@ import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; -import { RunContext, TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; +import { RunContext, TaskManagerSetupContract, TaskCost } from '@kbn/task-manager-plugin/server'; import { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; import { ActionType as CommonActionType, areValidFeatures } from '../common'; import { ActionsConfigurationUtilities } from './actions_config'; @@ -194,6 +194,7 @@ export class ActionTypeRegistry { [`actions:${actionType.id}`]: { title: actionType.name, maxAttempts, + cost: TaskCost.Tiny, createTaskRunner: (context: RunContext) => this.taskRunnerFactory.create(context), }, }); diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx index 91e18ea7b44214..2a9591bb415a6f 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx @@ -6,7 +6,7 @@ */ import type { FC } from 'react'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { EuiBasicTableColumn, EuiTableSelectionType } from '@elastic/eui'; import { @@ -16,12 +16,12 @@ import { EuiToolTip, EuiIcon, } from '@elastic/eui'; +import type { Action } from '@elastic/eui/src/components/basic_table/action_types'; import { i18n } from '@kbn/i18n'; import type { UseTableState } from '@kbn/ml-in-memory-table'; import { css } from '@emotion/react'; -import { QUERY_MODE } from '@kbn/aiops-log-pattern-analysis/get_category_query'; import type { Category } from '@kbn/aiops-log-pattern-analysis/types'; import { useEuiTheme } from '../../../hooks/use_eui_theme'; @@ -32,34 +32,36 @@ import type { EventRate } from '../use_categorize_request'; import { ExpandedRow } from './expanded_row'; import { FormattedPatternExamples, FormattedTokens } from '../format_category'; -import type { OpenInDiscover } from './use_open_in_discover'; interface Props { categories: Category[]; eventRate: EventRate; - pinnedCategory: Category | null; - setPinnedCategory: (category: Category | null) => void; - highlightedCategory: Category | null; - setHighlightedCategory: (category: Category | null) => void; + mouseOver?: { + pinnedCategory: Category | null; + setPinnedCategory: (category: Category | null) => void; + highlightedCategory: Category | null; + setHighlightedCategory: (category: Category | null) => void; + }; setSelectedCategories: (category: Category[]) => void; - openInDiscover: OpenInDiscover; tableState: UseTableState; + actions: Array>; enableRowActions?: boolean; displayExamples?: boolean; + selectable?: boolean; + onRenderComplete?: () => void; } export const CategoryTable: FC = ({ categories, eventRate, - pinnedCategory, - setPinnedCategory, - highlightedCategory, - setHighlightedCategory, + mouseOver, setSelectedCategories, - openInDiscover, tableState, + actions, enableRowActions = true, displayExamples = true, + selectable = true, + onRenderComplete, }) => { const euiTheme = useEuiTheme(); const primaryBackgroundColor = useEuiBackgroundColor('primary'); @@ -73,8 +75,6 @@ export const CategoryTable: FC = ({ return categories.some((category) => category.sparkline !== undefined); }, [categories]); - const { labels: openInDiscoverLabels, openFunction: openInDiscoverFunction } = openInDiscover; - const toggleDetails = useCallback( (category: Category) => { const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; @@ -134,24 +134,7 @@ export const CategoryTable: FC = ({ }), sortable: false, width: '65px', - actions: [ - { - name: openInDiscoverLabels.singleSelect.in, - description: openInDiscoverLabels.singleSelect.in, - icon: 'plusInCircle', - type: 'icon', - 'data-test-subj': 'aiopsLogPatternsActionFilterInButton', - onClick: (category) => openInDiscoverFunction(QUERY_MODE.INCLUDE, category), - }, - { - name: openInDiscoverLabels.singleSelect.out, - description: openInDiscoverLabels.singleSelect.out, - icon: 'minusInCircle', - type: 'icon', - 'data-test-subj': 'aiopsLogPatternsActionFilterOutButton', - onClick: (category) => openInDiscoverFunction(QUERY_MODE.EXCLUDE, category), - }, - ], + actions, }, ] as Array>; @@ -214,23 +197,29 @@ export const CategoryTable: FC = ({ }); } - const selectionValue: EuiTableSelectionType | undefined = { - selectable: () => true, - onSelectionChange: (selectedItems) => setSelectedCategories(selectedItems), - }; + const selectionValue: EuiTableSelectionType | undefined = selectable + ? { + selectable: () => true, + onSelectionChange: (selectedItems) => setSelectedCategories(selectedItems), + } + : undefined; const getRowStyle = (category: Category) => { + if (mouseOver === undefined) { + return {}; + } + if ( - pinnedCategory && - pinnedCategory.key === category.key && - pinnedCategory.key === category.key + mouseOver.pinnedCategory && + mouseOver.pinnedCategory.key === category.key && + mouseOver.pinnedCategory.key === category.key ) { return { backgroundColor: primaryBackgroundColor, }; } - if (highlightedCategory && highlightedCategory.key === category.key) { + if (mouseOver.highlightedCategory && mouseOver.highlightedCategory.key === category.key) { return { backgroundColor: euiTheme.euiColorLightestShade, }; @@ -251,39 +240,66 @@ export const CategoryTable: FC = ({ }, }); + const chartWrapperRef = useRef(null); + + const renderCompleteListener = useCallback( + (event: Event) => { + if (event.target !== chartWrapperRef.current) { + return; + } + if (typeof onRenderComplete === 'function') { + onRenderComplete(); + } + }, + [onRenderComplete] + ); + + useEffect(() => { + if (!chartWrapperRef.current) { + throw new Error('Reference to the chart wrapper is not set'); + } + const chartWrapper = chartWrapperRef.current; + chartWrapper.addEventListener('renderComplete', renderCompleteListener); + return () => { + chartWrapper.removeEventListener('renderComplete', renderCompleteListener); + }; + }, [renderCompleteListener]); + return ( - - compressed - items={categories} - columns={columns} - selection={selectionValue} - itemId="key" - onTableChange={onTableChange} - pagination={pagination} - sorting={sorting} - data-test-subj="aiopsLogPatternsTable" - itemIdToExpandedRowMap={itemIdToExpandedRowMap} - css={tableStyle} - rowProps={(category) => { - return enableRowActions - ? { - onClick: () => { - if (category.key === pinnedCategory?.key) { - setPinnedCategory(null); - } else { - setPinnedCategory(category); - } - }, - onMouseEnter: () => { - setHighlightedCategory(category); - }, - onMouseLeave: () => { - setHighlightedCategory(null); - }, - style: getRowStyle(category), - } - : undefined; - }} - /> +
+ + compressed + items={categories} + columns={columns} + selection={selectionValue} + itemId="key" + onTableChange={onTableChange} + pagination={pagination} + sorting={sorting} + data-test-subj="aiopsLogPatternsTable" + itemIdToExpandedRowMap={itemIdToExpandedRowMap} + css={tableStyle} + rowProps={(category) => { + return mouseOver + ? { + onClick: () => { + if (category.key === mouseOver.pinnedCategory?.key) { + mouseOver.setPinnedCategory(null); + } else { + mouseOver.setPinnedCategory(category); + } + }, + onMouseEnter: () => { + mouseOver.setHighlightedCategory(category); + }, + onMouseLeave: () => { + mouseOver.setHighlightedCategory(null); + }, + style: getRowStyle(category), + } + : undefined; + }} + /> +
); }; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/labels.ts b/x-pack/plugins/aiops/public/components/log_categorization/category_table/labels.ts index 674f7b8da0b609..bc94a5ec6918ba 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/category_table/labels.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/labels.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; -export function getLabels(isFlyout: boolean) { +export function getLabels(inDiscover: boolean) { const flyoutFilterIn = (single: boolean) => i18n.translate('xpack.aiops.logCategorization.flyout.filterIn', { defaultMessage: 'Filter for {values, plural, one {pattern} other {patterns}}', @@ -38,25 +38,25 @@ export function getLabels(isFlyout: boolean) { }, }); - return isFlyout + return inDiscover ? { multiSelect: { - in: flyoutFilterIn(false), - out: flyoutFilterOut(false), + in: aiopsFilterIn(false), + out: aiopsFilterOut(false), }, singleSelect: { - in: flyoutFilterIn(true), - out: flyoutFilterOut(true), + in: aiopsFilterIn(true), + out: aiopsFilterOut(true), }, } : { multiSelect: { - in: aiopsFilterIn(false), - out: aiopsFilterOut(false), + in: flyoutFilterIn(false), + out: flyoutFilterOut(false), }, singleSelect: { - in: aiopsFilterIn(true), - out: aiopsFilterOut(true), + in: flyoutFilterIn(true), + out: flyoutFilterOut(true), }, }; } diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/table_header.tsx b/x-pack/plugins/aiops/public/components/log_categorization/category_table/table_header.tsx index d733c050fc3b8a..9157b4994adb4c 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/category_table/table_header.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/table_header.tsx @@ -58,7 +58,8 @@ export const OpenInDiscoverButtons: FC<{ openInDiscover: OpenInDiscover; showTex openInDiscover, showText = true, }) => { - const { labels, openFunction } = openInDiscover; + const { getLabels, openFunction } = openInDiscover; + const labels = getLabels(false); return ( @@ -67,7 +68,7 @@ export const OpenInDiscoverButtons: FC<{ openInDiscover: OpenInDiscover; showTex openFunction(QUERY_MODE.INCLUDE)} + onClick={() => openFunction(QUERY_MODE.INCLUDE, true)} iconType="plusInCircle" iconSide="left" > @@ -80,7 +81,7 @@ export const OpenInDiscoverButtons: FC<{ openInDiscover: OpenInDiscover; showTex openFunction(QUERY_MODE.EXCLUDE)} + onClick={() => openFunction(QUERY_MODE.EXCLUDE, true)} iconType="minusInCircle" iconSide="left" > diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/use_actions.tsx b/x-pack/plugins/aiops/public/components/log_categorization/category_table/use_actions.tsx new file mode 100644 index 00000000000000..cb7567743d9ed3 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/use_actions.tsx @@ -0,0 +1,78 @@ +/* + * 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 { useCallback } from 'react'; + +import type { Action } from '@elastic/eui/src/components/basic_table/action_types'; + +import { QUERY_MODE } from '@kbn/aiops-log-pattern-analysis/get_category_query'; +import type { Filter } from '@kbn/es-query'; +import type { Category } from '@kbn/aiops-log-pattern-analysis/types'; +import type { DataViewField } from '@kbn/data-views-plugin/common'; +import type { TimefilterContract } from '@kbn/data-plugin/public'; +import type { CategorizationAdditionalFilter } from '@kbn/aiops-log-pattern-analysis/create_category_request'; +import type { LogCategorizationAppState } from '../../../application/url_state/log_pattern_analysis'; +import type { OpenInDiscover } from './use_open_in_discover'; +import { useOpenInDiscover } from './use_open_in_discover'; + +export interface UseActions { + getActions: (navigateToDiscover: boolean) => Array>; + openInDiscover: OpenInDiscover; +} + +export function useActions( + dataViewId: string, + selectedField: DataViewField | string | undefined, + selectedCategories: Category[], + aiopsListState: LogCategorizationAppState, + timefilter: TimefilterContract, + onAddFilter?: (values: Filter, alias?: string) => void, + additionalFilter?: CategorizationAdditionalFilter, + onClose: () => void = () => {} +): UseActions { + const openInDiscover = useOpenInDiscover( + dataViewId, + selectedField ?? undefined, + selectedCategories, + aiopsListState, + timefilter, + onAddFilter, + additionalFilter + ); + + const { getLabels: getOpenInDiscoverLabels, openFunction: openInDiscoverFunction } = + openInDiscover; + + const getActions = useCallback( + (navigateToDiscover: boolean): Array> => { + const openInDiscoverLabels = getOpenInDiscoverLabels(navigateToDiscover); + return [ + { + name: openInDiscoverLabels.singleSelect.in, + description: openInDiscoverLabels.singleSelect.in, + icon: 'plusInCircle', + type: 'icon', + 'data-test-subj': 'aiopsLogPatternsActionFilterInButton', + onClick: (category) => + openInDiscoverFunction(QUERY_MODE.INCLUDE, navigateToDiscover, category), + }, + { + name: openInDiscoverLabels.singleSelect.out, + description: openInDiscoverLabels.singleSelect.out, + icon: 'minusInCircle', + type: 'icon', + 'data-test-subj': 'aiopsLogPatternsActionFilterOutButton', + onClick: (category) => + openInDiscoverFunction(QUERY_MODE.EXCLUDE, navigateToDiscover, category), + }, + ]; + }, + [getOpenInDiscoverLabels, openInDiscoverFunction] + ); + + return { getActions, openInDiscover }; +} diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/use_open_in_discover.ts b/x-pack/plugins/aiops/public/components/log_categorization/category_table/use_open_in_discover.ts index 963fd6b3a13e8c..6a0908fbde725a 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/category_table/use_open_in_discover.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/use_open_in_discover.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useCallback, useMemo } from 'react'; +import { useCallback } from 'react'; import moment from 'moment'; @@ -20,8 +20,8 @@ import type { LogCategorizationAppState } from '../../../application/url_state/l import { getLabels } from './labels'; export interface OpenInDiscover { - openFunction: (mode: QueryMode, category?: Category) => void; - labels: ReturnType; + openFunction: (mode: QueryMode, navigateToDiscover: boolean, category?: Category) => void; + getLabels: (navigateToDiscover: boolean) => ReturnType; count: number; } @@ -31,7 +31,6 @@ export function useOpenInDiscover( selectedCategories: Category[], aiopsListState: LogCategorizationAppState, timefilter: TimefilterContract, - navigateToDiscover?: boolean, onAddFilter?: (values: Filter, alias?: string) => void, additionalFilter?: CategorizationAdditionalFilter, onClose: () => void = () => {} @@ -39,7 +38,7 @@ export function useOpenInDiscover( const { openInDiscoverWithFilter } = useDiscoverLinks(); const openFunction = useCallback( - (mode: QueryMode, category?: Category) => { + (mode: QueryMode, navigateToDiscover: boolean, category?: Category) => { if ( onAddFilter !== undefined && selectedField !== undefined && @@ -80,7 +79,6 @@ export function useOpenInDiscover( [ onAddFilter, selectedField, - navigateToDiscover, additionalFilter, timefilter, openInDiscoverWithFilter, @@ -91,10 +89,5 @@ export function useOpenInDiscover( ] ); - const labels = useMemo(() => { - const isFlyout = onAddFilter !== undefined && onClose !== undefined; - return getLabels(isFlyout && navigateToDiscover === false); - }, [navigateToDiscover, onAddFilter, onClose]); - - return { openFunction, labels, count: selectedCategories.length }; + return { openFunction, getLabels, count: selectedCategories.length }; } diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_validation_callout.tsx b/x-pack/plugins/aiops/public/components/log_categorization/category_validation_callout.tsx index 966be3b100e74e..b201ef982d6a75 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/category_validation_callout.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_validation_callout.tsx @@ -29,6 +29,8 @@ export const FieldValidationCallout: FC = ({ validationResults }) => { return ( = ({ {renderViewModeToggle(data?.categories.length)} - <> - - - {selectedCategories.length > 0 ? ( - - - - ) : null} + + {selectedCategories.length > 0 ? ( - + - -
+ ) : null} + + + + +
+
+ loadCategories()} + minimumTimeRangeOption={minimumTimeRangeOption} + setMinimumTimeRangeOption={setMinimumTimeRangeOption} + categoryCount={data?.totalCategories} + /> +
+ {selectedField !== null && earliest !== undefined && latest !== undefined ? (
- loadCategories()} - minimumTimeRangeOption={minimumTimeRangeOption} - setMinimumTimeRangeOption={setMinimumTimeRangeOption} - categoryCount={data?.totalCategories} +
- {selectedField !== null && earliest !== undefined && latest !== undefined ? ( -
- -
- ) : null} -
-
- - + ) : null} +
+
+
diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/embeddable_menu.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/embeddable_menu.tsx index af40e305f558bc..42124e39c07c04 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/embeddable_menu.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/embeddable_menu.tsx @@ -36,9 +36,9 @@ interface Props { reload: () => void; } -const minimumTimeRangeOptions = Object.keys(MINIMUM_TIME_RANGE).map((value) => ({ - inputDisplay: value, - value: value as MinimumTimeRangeOption, +const minimumTimeRangeOptions = Object.entries(MINIMUM_TIME_RANGE).map(([key, { label }]) => ({ + inputDisplay: label, + value: key as MinimumTimeRangeOption, })); export const EmbeddableMenu: FC = ({ @@ -62,7 +62,7 @@ export const EmbeddableMenu: FC = ({ size="s" iconType="controlsHorizontal" onClick={() => togglePopover()} - // @ts-ignore - subdued does work + // @ts-expect-error - subdued does work color="subdued" aria-label={i18n.translate('xpack.aiops.logCategorization.embeddableMenu.aria', { defaultMessage: 'Pattern analysis options', @@ -91,54 +91,11 @@ export const EmbeddableMenu: FC = ({ - - - {i18n.translate( - 'xpack.aiops.logCategorization.embeddableMenu.minimumTimeRangeOptionsRowLabel', - { - defaultMessage: 'Minimum time range', - } - )} - - - - - - - - } - helpText={ - <> - {categoryCount !== undefined && minimumTimeRangeOption !== 'No minimum' ? ( - <> - - - ) : null} - - } - > - - + @@ -147,3 +104,78 @@ export const EmbeddableMenu: FC = ({ ); }; + +interface PatternAnalysisSettingsProps { + minimumTimeRangeOption: MinimumTimeRangeOption; + setMinimumTimeRangeOption: (w: MinimumTimeRangeOption) => void; + categoryCount: number | undefined; + compressed?: boolean; +} + +export const PatternAnalysisSettings: FC = ({ + minimumTimeRangeOption, + setMinimumTimeRangeOption, + categoryCount, + compressed = false, +}) => { + return ( + <> + + + {i18n.translate( + 'xpack.aiops.logCategorization.embeddableMenu.minimumTimeRangeOptionsRowLabel', + { + defaultMessage: 'Minimum time range', + } + )} + + + + + + + + } + helpText={ + <> + {categoryCount !== undefined && minimumTimeRangeOption !== 'No minimum' ? ( + <> + + + ) : null} + + } + > + + + + ); +}; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/field_selector.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/field_selector.tsx index 1941e2d8148d05..126eb9c53695b8 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/field_selector.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/field_selector.tsx @@ -25,17 +25,13 @@ interface Props { fields: DataViewField[]; selectedField: DataViewField | null; setSelectedField: (field: DataViewField) => void; + WarningComponent?: FC; } export const SelectedField: FC = ({ fields, selectedField, setSelectedField }) => { const [showPopover, setShowPopover] = useState(false); const togglePopover = () => setShowPopover(!showPopover); - const fieldOptions = useMemo( - () => fields.map((field) => ({ inputDisplay: field.name, value: field })), - [fields] - ); - const button = ( = ({ fields, selectedField, setSelectedFie button={button} className="unifiedDataTableToolbarControlButton" > - + + + ); +}; + +export const FieldSelector: FC = ({ + fields, + selectedField, + setSelectedField, + WarningComponent, +}) => { + const fieldOptions = useMemo( + () => fields.map((field) => ({ inputDisplay: field.name, value: field })), + [fields] + ); + + const label = i18n.translate( + 'xpack.aiops.logCategorization.embeddableMenu.selectedFieldRowLabel', + { + defaultMessage: 'Selected field', + } + ); + + return ( + <> + {WarningComponent !== undefined ? : null} + + - + ); }; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/index.ts b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/index.ts index 8cc8fcbad313aa..cfeafd6bac0eea 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/index.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { LogCategorizationEmbeddable } from './log_categorization_for_embeddable'; -export { LogCategorizationWrapper } from './log_categorization_wrapper'; +export { LogCategorizationDiscover } from './log_categorization_for_discover'; +export { LogCategorizationDiscoverWrapper } from './log_categorization_for_discover_wrapper'; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/log_categorization_for_discover.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/log_categorization_for_discover.tsx new file mode 100644 index 00000000000000..27d680fc1769c5 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/log_categorization_for_discover.tsx @@ -0,0 +1,470 @@ +/* + * 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 { FC } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, useEuiPaddingSize } from '@elastic/eui'; + +import type { DataViewField } from '@kbn/data-views-plugin/public'; +import { i18n } from '@kbn/i18n'; +import type { Filter } from '@kbn/es-query'; +import { buildEmptyFilter } from '@kbn/es-query'; +import { usePageUrlState } from '@kbn/ml-url-state'; +import type { FieldValidationResults } from '@kbn/ml-category-validator'; + +import type { Category } from '@kbn/aiops-log-pattern-analysis/types'; + +import type { CategorizationAdditionalFilter } from '@kbn/aiops-log-pattern-analysis/create_category_request'; +import { AIOPS_TELEMETRY_ID } from '@kbn/aiops-common/constants'; +import type { EmbeddablePatternAnalysisInput } from '@kbn/aiops-log-pattern-analysis/embeddable'; +import { css } from '@emotion/react'; +import { useTableState } from '@kbn/ml-in-memory-table/hooks/use_table_state'; +import useMountedState from 'react-use/lib/useMountedState'; +import { + type LogCategorizationPageUrlState, + getDefaultLogCategorizationAppState, +} from '../../../application/url_state/log_pattern_analysis'; +import { createMergedEsQuery } from '../../../application/utils/search_utils'; +import { useData } from '../../../hooks/use_data'; +import { useSearch } from '../../../hooks/use_search'; +import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context'; + +import { useCategorizeRequest } from '../use_categorize_request'; +import type { EventRate } from '../use_categorize_request'; +import { CategoryTable } from '../category_table'; +import { InformationText } from '../information_text'; +import { LoadingCategorization } from '../loading_categorization'; +import { useValidateFieldRequest } from '../use_validate_category_field'; +import { FieldValidationCallout } from '../category_validation_callout'; +import { useMinimumTimeRange } from './use_minimum_time_range'; + +import { createAdditionalConfigHash, createDocumentStatsHash, getMessageField } from '../utils'; +import { DiscoverTabs } from './discover_tabs'; +import { useRandomSamplerStorage } from '../sampling_menu'; +import { useActions } from '../category_table/use_actions'; + +export interface LogCategorizationEmbeddableProps { + input: Readonly; + renderViewModeToggle: (patternCount?: number) => React.ReactElement; +} + +const BAR_TARGET = 20; + +export const LogCategorizationDiscover: FC = ({ + input, + renderViewModeToggle, +}) => { + const { + notifications: { toasts }, + data: { + query: { getState, filterManager }, + }, + uiSettings, + embeddingOrigin, + } = useAiopsAppContext(); + const tablePadding = useEuiPaddingSize('xs'); + + const { dataView, savedSearch } = input; + + const { runValidateFieldRequest, cancelRequest: cancelValidationRequest } = + useValidateFieldRequest(); + const { + getMinimumTimeRange, + cancelRequest: cancelWiderTimeRangeRequest, + minimumTimeRangeOption, + setMinimumTimeRangeOption, + } = useMinimumTimeRange(); + const { filters, query } = useMemo(() => getState(), [getState]); + + const isMounted = useMountedState(); + const randomSamplerStorage = useRandomSamplerStorage(); + const { + runCategorizeRequest, + cancelRequest: cancelCategorizationRequest, + randomSampler, + } = useCategorizeRequest(randomSamplerStorage); + const [stateFromUrl] = usePageUrlState( + 'logCategorization', + getDefaultLogCategorizationAppState({ + searchQuery: createMergedEsQuery(query, filters, dataView, uiSettings), + }) + ); + const [selectedCategories, setSelectedCategories] = useState([]); + const [selectedField, setSelectedField] = useState(null); + const [fields, setFields] = useState([]); + const [currentDocumentStatsHash, setCurrentDocumentStatsHash] = useState(null); + const [previousDocumentStatsHash, setPreviousDocumentStatsHash] = useState(0); + const [currentAdditionalConfigsHash, setCurrentAdditionalConfigsHash] = useState( + null + ); + const [previousAdditionalConfigsHash, setPreviousAdditionalConfigsHash] = useState( + null + ); + const [loading, setLoading] = useState(null); + const [eventRate, setEventRate] = useState([]); + const [data, setData] = useState<{ + categories: Category[]; + displayExamples: boolean; + totalCategories: number; + } | null>(null); + const [fieldValidationResult, setFieldValidationResult] = useState( + null + ); + const tableState = useTableState([], 'key'); + + useEffect( + function initFields() { + setCurrentDocumentStatsHash(null); + setSelectedField(null); + setLoading(null); + const { dataViewFields, messageField } = getMessageField(dataView); + setFields(dataViewFields); + setSelectedField(messageField); + }, + [dataView] + ); + + const cancelRequest = useCallback(() => { + cancelWiderTimeRangeRequest(); + cancelValidationRequest(); + cancelCategorizationRequest(); + }, [cancelCategorizationRequest, cancelValidationRequest, cancelWiderTimeRangeRequest]); + + useEffect( + function cancelRequestOnLeave() { + return () => { + cancelRequest(); + }; + }, + [cancelRequest] + ); + + const { searchQuery } = useSearch( + { dataView, savedSearch: savedSearch ?? null }, + stateFromUrl, + true + ); + + const { documentStats, timefilter, earliest, latest, intervalMs, forceRefresh } = useData( + dataView, + 'log_categorization', + searchQuery, + () => {}, + undefined, + undefined, + BAR_TARGET, + false + ); + + const onAddFilter = useCallback( + (values: Filter, alias?: string) => { + const filter = buildEmptyFilter(false, dataView.id); + if (alias) { + filter.meta.alias = alias; + } + filter.query = values.query; + if (typeof input.switchToDocumentView === 'function') { + input.switchToDocumentView().finally(() => { + filterManager.addFilters([filter]); + }); + } + }, + [dataView.id, filterManager, input] + ); + + const { getActions, openInDiscover } = useActions( + dataView.id!, + selectedField ?? undefined, + selectedCategories, + stateFromUrl, + timefilter, + onAddFilter, + undefined + ); + + useEffect( + function createDocumentStatHash() { + if (documentStats.documentCountStats === undefined) { + return; + } + + const hash = createDocumentStatsHash(documentStats); + if (hash !== previousDocumentStatsHash) { + setCurrentDocumentStatsHash(hash); + setData(null); + setFieldValidationResult(null); + } + }, + [documentStats, previousDocumentStatsHash] + ); + + useEffect( + function createAdditionalConfigHash2() { + if (!selectedField?.name) { + return; + } + + const hash = createAdditionalConfigHash([selectedField.name, minimumTimeRangeOption]); + if (hash !== previousAdditionalConfigsHash) { + setCurrentAdditionalConfigsHash(hash); + setData(null); + setFieldValidationResult(null); + } + }, + [minimumTimeRangeOption, previousAdditionalConfigsHash, selectedField] + ); + + const loadCategories = useCallback(async () => { + const { getIndexPattern, timeFieldName: timeField } = dataView; + const index = getIndexPattern(); + + if ( + loading === true || + selectedField === null || + timeField === undefined || + earliest === undefined || + latest === undefined || + minimumTimeRangeOption === undefined || + isMounted() !== true + ) { + return; + } + + cancelRequest(); + + setLoading(true); + setData(null); + setFieldValidationResult(null); + + const additionalFilter: CategorizationAdditionalFilter = { + from: earliest, + to: latest, + }; + + const runtimeMappings = dataView.getRuntimeMappings(); + + try { + const timeRange = await getMinimumTimeRange( + index, + timeField, + additionalFilter, + minimumTimeRangeOption, + searchQuery, + runtimeMappings + ); + + if (isMounted() === false) { + return; + } + + const [validationResult, categorizationResult] = await Promise.all([ + runValidateFieldRequest( + index, + selectedField.name, + timeField, + timeRange, + searchQuery, + runtimeMappings, + { + [AIOPS_TELEMETRY_ID.AIOPS_ANALYSIS_RUN_ORIGIN]: embeddingOrigin, + } + ), + runCategorizeRequest( + index, + selectedField.name, + timeField, + { to: timeRange.to, from: timeRange.from }, + searchQuery, + runtimeMappings, + intervalMs, + timeRange.useSubAgg ? additionalFilter : undefined + ), + ]); + + if (isMounted() === false) { + return; + } + + setFieldValidationResult(validationResult); + const { categories, hasExamples } = categorizationResult; + + if (timeRange.useSubAgg) { + const categoriesInBucket = categorizationResult.categories + .map((category) => ({ + ...category, + count: category.subFieldCount ?? category.subTimeRangeCount!, + examples: category.subFieldExamples!, + sparkline: category.subFieldSparkline, + })) + .filter((category) => category.count > 0) + .sort((a, b) => b.count - a.count); + setData({ + categories: categoriesInBucket, + displayExamples: hasExamples, + totalCategories: categories.length, + }); + } else { + setData({ + categories, + displayExamples: hasExamples, + totalCategories: categories.length, + }); + } + } catch (error) { + if (error.name !== 'AbortError') { + toasts.addError(error, { + title: i18n.translate('xpack.aiops.logCategorization.errorLoadingCategories', { + defaultMessage: 'Error loading categories', + }), + }); + } + } + + if (isMounted() === true) { + setLoading(false); + } + }, [ + dataView, + loading, + selectedField, + earliest, + latest, + minimumTimeRangeOption, + isMounted, + cancelRequest, + getMinimumTimeRange, + searchQuery, + runValidateFieldRequest, + embeddingOrigin, + runCategorizeRequest, + intervalMs, + toasts, + ]); + + useEffect( + function triggerAnalysis() { + const buckets = documentStats.documentCountStats?.buckets; + if (buckets === undefined || currentDocumentStatsHash === null) { + return; + } + + if ( + currentDocumentStatsHash !== previousDocumentStatsHash || + (currentAdditionalConfigsHash !== previousAdditionalConfigsHash && + currentDocumentStatsHash !== null) + ) { + randomSampler.setDocCount(documentStats.totalCount); + setEventRate( + Object.entries(buckets).map(([key, docCount]) => ({ + key: +key, + docCount, + })) + ); + loadCategories(); + setPreviousDocumentStatsHash(currentDocumentStatsHash); + setPreviousAdditionalConfigsHash(currentAdditionalConfigsHash); + } + }, + [ + loadCategories, + randomSampler, + previousDocumentStatsHash, + fieldValidationResult, + currentDocumentStatsHash, + currentAdditionalConfigsHash, + documentStats.documentCountStats?.buckets, + documentStats.totalCount, + previousAdditionalConfigsHash, + ] + ); + + useEffect( + function refreshTriggeredFromButton() { + if (input.lastReloadRequestTime !== undefined) { + setPreviousDocumentStatsHash(0); + setPreviousAdditionalConfigsHash(null); + forceRefresh(); + } + }, + // stop infinite loop from forceRefresh dependency + // eslint-disable-next-line react-hooks/exhaustive-deps + [input.lastReloadRequestTime] + ); + const style = css({ + overflowY: 'auto', + '.kbnDocTableWrapper': { + overflowX: 'hidden', + }, + }); + + const actions = getActions(false); + + return ( + <> + + + + + + + + <> + + {(loading ?? true) === true ? ( + + ) : null} + + {loading === false && + data !== null && + data.categories.length > 0 && + selectedField !== null ? ( + + ) : null} + + + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default LogCategorizationDiscover; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/log_categorization_wrapper.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/log_categorization_for_discover_wrapper.tsx similarity index 91% rename from x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/log_categorization_wrapper.tsx rename to x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/log_categorization_for_discover_wrapper.tsx index 49a8219bc880a2..1af4f03a06bb33 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/log_categorization_wrapper.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/log_categorization_for_discover_wrapper.tsx @@ -24,8 +24,8 @@ import { Storage } from '@kbn/kibana-utils-plugin/public'; import { AIOPS_STORAGE_KEYS } from '../../../types/storage'; import type { AiopsAppDependencies } from '../../../hooks/use_aiops_app_context'; import { AiopsAppContext } from '../../../hooks/use_aiops_app_context'; -import type { LogCategorizationEmbeddableProps } from './log_categorization_for_embeddable'; -import { LogCategorizationEmbeddable } from './log_categorization_for_embeddable'; +import type { LogCategorizationEmbeddableProps } from './log_categorization_for_discover'; +import { LogCategorizationDiscover } from './log_categorization_for_discover'; export interface EmbeddableLogCategorizationDeps { theme: ThemeServiceStart; @@ -49,7 +49,7 @@ export interface LogCategorizationEmbeddableWrapperProps { const localStorage = new Storage(window.localStorage); -export const LogCategorizationWrapper: FC = ({ +export const LogCategorizationDiscoverWrapper: FC = ({ deps, props, embeddingOrigin, @@ -71,7 +71,7 @@ export const LogCategorizationWrapper: FC - @@ -84,4 +84,4 @@ export const LogCategorizationWrapper: FC; - renderViewModeToggle: (patternCount?: number) => React.ReactElement; -} +export type LogCategorizationEmbeddableProps = Readonly< + EmbeddablePatternAnalysisInput & PatternAnalysisProps +>; const BAR_TARGET = 20; export const LogCategorizationEmbeddable: FC = ({ - input, - renderViewModeToggle, + dataView, + savedSearch, + fieldName, + minimumTimeRangeOption, + randomSamplerMode, + randomSamplerProbability, + onChange, + onRenderComplete, + timeRange, + lastReloadRequestTime, }) => { const { notifications: { toasts }, data: { - query: { getState, filterManager }, + query: { filterManager }, }, uiSettings, embeddingOrigin, } = useAiopsAppContext(); - const tablePadding = useEuiPaddingSize('xs'); - const { dataView, savedSearch } = input; + const { filters, query } = useFilterQueryUpdates(); const { runValidateFieldRequest, cancelRequest: cancelValidationRequest } = useValidateFieldRequest(); - const { - getMinimumTimeRange, - cancelRequest: cancelWiderTimeRangeRequest, - minimumTimeRangeOption, - setMinimumTimeRangeOption, - } = useMinimumTimeRange(); - const { filters, query } = useMemo(() => getState(), [getState]); + const { getMinimumTimeRange, cancelRequest: cancelWiderTimeRangeRequest } = useMinimumTimeRange(); - const mounted = useRef(false); + const isMounted = useMountedState(); const { runCategorizeRequest, cancelRequest: cancelCategorizationRequest, randomSampler, - } = useCategorizeRequest(); - const [stateFromUrl] = usePageUrlState( - 'logCategorization', - getDefaultLogCategorizationAppState({ - searchQuery: createMergedEsQuery(query, filters, dataView, uiSettings), - }) - ); - const [highlightedCategory, setHighlightedCategory] = useState(null); + } = useCategorizeRequest({ + randomSamplerMode, + setRandomSamplerMode: () => {}, + randomSamplerProbability, + setRandomSamplerProbability: () => {}, + }); + + const appState = getDefaultLogCategorizationAppState({ + searchQuery: createMergedEsQuery(query, filters, dataView, uiSettings), + filters, + }); + const { searchQuery } = useSearch({ dataView, savedSearch: savedSearch ?? null }, appState, true); + const [selectedCategories, setSelectedCategories] = useState([]); - const [selectedField, setSelectedField] = useState(null); - const [fields, setFields] = useState([]); - const [currentDocumentStatsHash, setCurrentDocumentStatsHash] = useState(null); - const [previousDocumentStatsHash, setPreviousDocumentStatsHash] = useState(0); - const [currentAdditionalConfigsHash, setCurrentAdditionalConfigsHash] = useState( - null - ); - const [previousAdditionalConfigsHash, setPreviousAdditionalConfigsHash] = useState( - null - ); + const [loading, setLoading] = useState(null); const [eventRate, setEventRate] = useState([]); - const [pinnedCategory, setPinnedCategory] = useState(null); const [data, setData] = useState<{ categories: Category[]; displayExamples: boolean; @@ -117,12 +107,7 @@ export const LogCategorizationEmbeddable: FC = useEffect( function initFields() { - setCurrentDocumentStatsHash(null); - setSelectedField(null); setLoading(null); - const { dataViewFields, messageField } = getMessageField(dataView); - setFields(dataViewFields); - setSelectedField(messageField); }, [dataView] ); @@ -131,24 +116,27 @@ export const LogCategorizationEmbeddable: FC = cancelWiderTimeRangeRequest(); cancelValidationRequest(); cancelCategorizationRequest(); + setLoading(false); }, [cancelCategorizationRequest, cancelValidationRequest, cancelWiderTimeRangeRequest]); useEffect( function cancelRequestOnLeave() { - mounted.current = true; return () => { - mounted.current = false; cancelRequest(); }; }, - [cancelRequest, mounted] + [cancelRequest] ); - const { searchQuery } = useSearch( - { dataView, savedSearch: savedSearch ?? null }, - stateFromUrl, - true - ); + const timeRangeParsed = useMemo(() => { + if (timeRange) { + const min = datemath.parse(timeRange.from); + const max = datemath.parse(timeRange.to); + if (min && max) { + return { min, max }; + } + } + }, [timeRange]); const { documentStats, timefilter, earliest, latest, intervalMs, forceRefresh } = useData( dataView, @@ -158,81 +146,45 @@ export const LogCategorizationEmbeddable: FC = undefined, undefined, BAR_TARGET, - false + false, + timeRangeParsed ); const onAddFilter = useCallback( (values: Filter, alias?: string) => { - if (input.switchToDocumentView === undefined) { - return; - } - const filter = buildEmptyFilter(false, dataView.id); if (alias) { filter.meta.alias = alias; } filter.query = values.query; - input.switchToDocumentView(); filterManager.addFilters([filter]); }, - [dataView.id, filterManager, input] + [dataView.id, filterManager] ); - const openInDiscover = useOpenInDiscover( + const { getActions } = useActions( dataView.id!, - selectedField ?? undefined, + dataView.fields.find((field) => field.name === fieldName), selectedCategories, - stateFromUrl, + appState, timefilter, - false, onAddFilter, undefined ); - useEffect( - function createDocumentStatHash() { - if (documentStats.documentCountStats === undefined) { - return; - } - - const hash = createDocumentStatsHash(documentStats); - if (hash !== previousDocumentStatsHash) { - setCurrentDocumentStatsHash(hash); - setData(null); - setFieldValidationResult(null); - } - }, - [documentStats, previousDocumentStatsHash] - ); - - useEffect( - function createAdditionalConfigHash2() { - if (!selectedField?.name) { - return; - } - - const hash = createAdditionalConfigHash([selectedField.name, minimumTimeRangeOption]); - if (hash !== previousAdditionalConfigsHash) { - setCurrentAdditionalConfigsHash(hash); - setData(null); - setFieldValidationResult(null); - } - }, - [minimumTimeRangeOption, previousAdditionalConfigsHash, selectedField] - ); - const loadCategories = useCallback(async () => { const { getIndexPattern, timeFieldName: timeField } = dataView; const index = getIndexPattern(); if ( loading === true || - selectedField === null || + fieldName === null || + fieldName === undefined || timeField === undefined || earliest === undefined || latest === undefined || minimumTimeRangeOption === undefined || - mounted.current !== true + isMounted() !== true ) { return; } @@ -250,7 +202,7 @@ export const LogCategorizationEmbeddable: FC = const runtimeMappings = dataView.getRuntimeMappings(); try { - const timeRange = await getMinimumTimeRange( + const minTimeRange = await getMinimumTimeRange( index, timeField, additionalFilter, @@ -259,16 +211,16 @@ export const LogCategorizationEmbeddable: FC = runtimeMappings ); - if (mounted.current !== true) { + if (isMounted() !== true) { return; } const [validationResult, categorizationResult] = await Promise.all([ runValidateFieldRequest( index, - selectedField.name, + fieldName, timeField, - timeRange, + minTimeRange, searchQuery, runtimeMappings, { @@ -277,24 +229,24 @@ export const LogCategorizationEmbeddable: FC = ), runCategorizeRequest( index, - selectedField.name, + fieldName, timeField, - { to: timeRange.to, from: timeRange.from }, + { to: minTimeRange.to, from: minTimeRange.from }, searchQuery, runtimeMappings, intervalMs, - timeRange.useSubAgg ? additionalFilter : undefined + minTimeRange.useSubAgg ? additionalFilter : undefined ), ]); - if (mounted.current !== true) { + if (isMounted() !== true) { return; } setFieldValidationResult(validationResult); const { categories, hasExamples } = categorizationResult; - if (timeRange.useSubAgg) { + if (minTimeRange.useSubAgg) { const categoriesInBucket = categorizationResult.categories .map((category) => ({ ...category, @@ -317,9 +269,7 @@ export const LogCategorizationEmbeddable: FC = }); } } catch (error) { - if (error.name === 'AbortError') { - // ignore error - } else { + if (error.name !== 'AbortError') { toasts.addError(error, { title: i18n.translate('xpack.aiops.logCategorization.errorLoadingCategories', { defaultMessage: 'Error loading categories', @@ -328,144 +278,103 @@ export const LogCategorizationEmbeddable: FC = } } - if (mounted.current === true) { + if (isMounted() === true) { setLoading(false); } }, [ + cancelRequest, dataView, - loading, - selectedField, earliest, + embeddingOrigin, + fieldName, + getMinimumTimeRange, + intervalMs, + isMounted, latest, + loading, minimumTimeRangeOption, - cancelRequest, - getMinimumTimeRange, - searchQuery, - runValidateFieldRequest, - embeddingOrigin, runCategorizeRequest, - intervalMs, + runValidateFieldRequest, + searchQuery, toasts, ]); + useEffect( + function setOnChange() { + if (typeof onChange === 'function') { + onChange(data?.categories ?? []); + } + }, + [data, onChange] + ); + useEffect( function triggerAnalysis() { const buckets = documentStats.documentCountStats?.buckets; - if (buckets === undefined || currentDocumentStatsHash === null) { + if (buckets === undefined) { return; } - if ( - currentDocumentStatsHash !== previousDocumentStatsHash || - (currentAdditionalConfigsHash !== previousAdditionalConfigsHash && - currentDocumentStatsHash !== null) - ) { - randomSampler.setDocCount(documentStats.totalCount); - setEventRate( - Object.entries(buckets).map(([key, docCount]) => ({ - key: +key, - docCount, - })) - ); - loadCategories(); - setPreviousDocumentStatsHash(currentDocumentStatsHash); - setPreviousAdditionalConfigsHash(currentAdditionalConfigsHash); - } + randomSampler.setDocCount(documentStats.totalCount); + setEventRate( + Object.entries(buckets).map(([key, docCount]) => ({ + key: +key, + docCount, + })) + ); + + loadCategories(); }, + // eslint-disable-next-line react-hooks/exhaustive-deps [ - loadCategories, randomSampler, - previousDocumentStatsHash, - fieldValidationResult, - currentDocumentStatsHash, - currentAdditionalConfigsHash, documentStats.documentCountStats?.buckets, documentStats.totalCount, - previousAdditionalConfigsHash, + dataView.name, + fieldName, + minimumTimeRangeOption, + randomSamplerMode, + randomSamplerProbability, ] ); useEffect( function refreshTriggeredFromButton() { - if (input.lastReloadRequestTime !== undefined) { - setPreviousDocumentStatsHash(0); - setPreviousAdditionalConfigsHash(null); + if (lastReloadRequestTime !== undefined) { + cancelRequest(); forceRefresh(); } }, // eslint-disable-next-line react-hooks/exhaustive-deps - [input.lastReloadRequestTime] + [lastReloadRequestTime] ); - const style = css({ - overflowY: 'auto', - '.kbnDocTableWrapper': { - overflowX: 'hidden', - }, - }); + + const actions = [...getActions(false), ...getActions(true)]; return ( <> - + {(loading ?? true) === true ? : null} + + - - - - - - <> - - {(loading ?? true) === true ? ( - - ) : null} - - {loading === false && - data !== null && - data.categories.length > 0 && - selectedField !== null ? ( - - ) : null} - - - - + {loading === false && data !== null && data.categories.length > 0 && fieldName !== null ? ( + + ) : null} ); }; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/minimum_time_range.ts b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/minimum_time_range.ts index 07b5485be7bbde..623f76919063fd 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/minimum_time_range.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/minimum_time_range.ts @@ -4,16 +4,50 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { i18n } from '@kbn/i18n'; import type { unitOfTime } from 'moment'; export type MinimumTimeRangeOption = 'No minimum' | '1 week' | '1 month' | '3 months' | '6 months'; -type MinimumTimeRange = Record; +type MinimumTimeRange = Record< + MinimumTimeRangeOption, + { label: string; factor: number; unit: unitOfTime.Base } +>; export const MINIMUM_TIME_RANGE: MinimumTimeRange = { - 'No minimum': { factor: 0, unit: 'w' }, - '1 week': { factor: 1, unit: 'w' }, - '1 month': { factor: 1, unit: 'M' }, - '3 months': { factor: 3, unit: 'M' }, - '6 months': { factor: 6, unit: 'M' }, + 'No minimum': { + label: i18n.translate('xpack.aiops.logCategorization.minimumTimeRange.noMin', { + defaultMessage: 'No minimum', + }), + factor: 0, + unit: 'w', + }, + '1 week': { + label: i18n.translate('xpack.aiops.logCategorization.minimumTimeRange.1week', { + defaultMessage: '1 week', + }), + factor: 1, + unit: 'w', + }, + '1 month': { + label: i18n.translate('xpack.aiops.logCategorization.minimumTimeRange.1month', { + defaultMessage: '1 month', + }), + factor: 1, + unit: 'M', + }, + '3 months': { + label: i18n.translate('xpack.aiops.logCategorization.minimumTimeRange.3months', { + defaultMessage: '3 months', + }), + factor: 3, + unit: 'M', + }, + '6 months': { + label: i18n.translate('xpack.aiops.logCategorization.minimumTimeRange.6months', { + defaultMessage: '6 months', + }), + factor: 6, + unit: 'M', + }, }; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/selected_patterns.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/selected_patterns.tsx index 44c9014c1aee1b..ebbe9ed7fb1694 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/selected_patterns.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/selected_patterns.tsx @@ -19,7 +19,8 @@ import { QUERY_MODE } from '@kbn/aiops-log-pattern-analysis/get_category_query'; import type { OpenInDiscover } from '../category_table/use_open_in_discover'; export const SelectedPatterns: FC<{ openInDiscover: OpenInDiscover }> = ({ openInDiscover }) => { - const { labels, openFunction } = openInDiscover; + const { getLabels, openFunction } = openInDiscover; + const labels = getLabels(false); const [showMenu, setShowMenu] = useState(false); const togglePopover = () => setShowMenu(!showMenu); @@ -51,14 +52,14 @@ export const SelectedPatterns: FC<{ openInDiscover: OpenInDiscover }> = ({ openI openFunction(QUERY_MODE.INCLUDE)} + onClick={() => openFunction(QUERY_MODE.INCLUDE, false)} > {labels.multiSelect.in} , openFunction(QUERY_MODE.EXCLUDE)} + onClick={() => openFunction(QUERY_MODE.EXCLUDE, false)} > {labels.multiSelect.out} , diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/use_minimum_time_range.ts b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/use_minimum_time_range.ts index 6d32e0902f1c88..3bb3c0ab1ad5fd 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/use_minimum_time_range.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/use_minimum_time_range.ts @@ -54,6 +54,11 @@ export function useMinimumTimeRange() { signal: abortController.current.signal, }); + if (resp.end.epoch === null || resp.start.epoch === null) { + // epoch can be null if no data can be found. + return { ...timeRange, useSubAgg: false }; + } + // the index isn't big enough to get a wider time range const indexTimeRangeMs = resp.end.epoch - resp.start.epoch; if (indexTimeRangeMs < minimumTimeRangeMs) { diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_flyout.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_flyout.tsx index 99ce9f88c8d2c7..ec61dca179da2b 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_flyout.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_flyout.tsx @@ -46,13 +46,13 @@ import { useCategorizeRequest } from './use_categorize_request'; import type { EventRate } from './use_categorize_request'; import { CategoryTable } from './category_table'; import { InformationText } from './information_text'; -import { SamplingMenu } from './sampling_menu'; +import { SamplingMenu, useRandomSamplerStorage } from './sampling_menu'; import { LoadingCategorization } from './loading_categorization'; import { useValidateFieldRequest } from './use_validate_category_field'; import { FieldValidationCallout } from './category_validation_callout'; import { CreateCategorizationJobButton } from './create_categorization_job'; import { TableHeader } from './category_table/table_header'; -import { useOpenInDiscover } from './category_table/use_open_in_discover'; +import { useActions } from './category_table/use_actions'; enum SELECTED_TAB { BUCKET, @@ -93,23 +93,21 @@ export const LogCategorizationFlyout: FC = ({ const { filters, query } = useMemo(() => getState(), [getState]); const mounted = useRef(false); + const randomSamplerStorage = useRandomSamplerStorage(); const { runCategorizeRequest, cancelRequest: cancelCategorizationRequest, randomSampler, - } = useCategorizeRequest(); + } = useCategorizeRequest(randomSamplerStorage); const [stateFromUrl] = usePageUrlState( 'logCategorization', getDefaultLogCategorizationAppState({ searchQuery: createMergedEsQuery(query, filters, dataView, uiSettings), }) ); - const [highlightedCategory, setHighlightedCategory] = useState(null); const [selectedCategories, setSelectedCategories] = useState([]); - const [selectedSavedSearch /* , setSelectedSavedSearch*/] = useState(savedSearch); const [loading, setLoading] = useState(true); const [eventRate, setEventRate] = useState([]); - const [pinnedCategory, setPinnedCategory] = useState(null); const [data, setData] = useState<{ categories: Category[]; categoriesInBucket: Category[] | null; @@ -139,7 +137,7 @@ export const LogCategorizationFlyout: FC = ({ ); const { searchQueryLanguage, searchString, searchQuery } = useSearch( - { dataView, savedSearch: selectedSavedSearch }, + { dataView, savedSearch }, stateFromUrl, true ); @@ -154,13 +152,12 @@ export const LogCategorizationFlyout: FC = ({ BAR_TARGET ); - const openInDiscover = useOpenInDiscover( + const { getActions, openInDiscover } = useActions( dataView.id!, selectedField, selectedCategories, stateFromUrl, timefilter, - true, undefined, undefined ); @@ -292,6 +289,7 @@ export const LogCategorizationFlyout: FC = ({ randomSampler, ]); + const actions = getActions(true); const infoIconCss = { marginTop: euiTheme.size.m, marginLeft: euiTheme.size.xxs }; return ( @@ -417,15 +415,11 @@ export const LogCategorizationFlyout: FC = ({ : data.categories } eventRate={eventRate} - pinnedCategory={pinnedCategory} - setPinnedCategory={setPinnedCategory} - highlightedCategory={highlightedCategory} - setHighlightedCategory={setHighlightedCategory} enableRowActions={false} displayExamples={data.displayExamples} setSelectedCategories={setSelectedCategories} - openInDiscover={openInDiscover} tableState={tableState} + actions={actions} /> ) : null} diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx index 5709833d925d96..567201fff0dd23 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -49,12 +49,12 @@ import { useCategorizeRequest } from './use_categorize_request'; import { CategoryTable } from './category_table'; import { DocumentCountChart } from './document_count_chart'; import { InformationText } from './information_text'; -import { SamplingMenu } from './sampling_menu'; +import { SamplingMenu, useRandomSamplerStorage } from './sampling_menu'; import { useValidateFieldRequest } from './use_validate_category_field'; import { FieldValidationCallout } from './category_validation_callout'; import { createDocumentStatsHash } from './utils'; import { TableHeader } from './category_table/table_header'; -import { useOpenInDiscover } from './category_table/use_open_in_discover'; +import { useActions } from './category_table/use_actions'; const BAR_TARGET = 20; const DEFAULT_SELECTED_FIELD = 'message'; @@ -70,11 +70,12 @@ export const LogCategorizationPage: FC = ({ embeddin } = useAiopsAppContext(); const { dataView, savedSearch } = useDataSource(); + const randomSamplerStorage = useRandomSamplerStorage(); const { runCategorizeRequest, cancelRequest: cancelCategorizationRequest, randomSampler, - } = useCategorizeRequest(); + } = useCategorizeRequest(randomSamplerStorage); const { runValidateFieldRequest, cancelRequest: cancelValidationRequest } = useValidateFieldRequest(); const [stateFromUrl, setUrlState] = usePageUrlState( @@ -159,13 +160,12 @@ export const LogCategorizationPage: FC = ({ embeddin BAR_TARGET ); - const openInDiscover = useOpenInDiscover( + const { getActions, openInDiscover } = useActions( dataView.id!, selectedField, selectedCategories, stateFromUrl, timefilter, - true, undefined, undefined ); @@ -336,6 +336,8 @@ export const LogCategorizationPage: FC = ({ embeddin setUrlState({ field }); }; + const actions = getActions(true); + return ( @@ -435,14 +437,16 @@ export const LogCategorizationPage: FC = ({ embeddin ) : null} diff --git a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/index.ts b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/index.ts index 9959fd531cb9cf..ab0b3f54aa40bd 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/index.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/index.ts @@ -7,3 +7,4 @@ export { SamplingMenu } from './sampling_menu'; export { RandomSampler } from './random_sampler'; +export { useRandomSamplerStorage, type RandomSamplerStorage } from './use_random_sampler_storage'; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler.ts b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler.ts index 72ebd2c95b7775..48fc5f3b5f28ea 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler.ts @@ -8,6 +8,7 @@ import { BehaviorSubject } from 'rxjs'; import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; import { i18n } from '@kbn/i18n'; +import type { RandomSamplerStorage } from './use_random_sampler_storage'; export const RANDOM_SAMPLER_PROBABILITIES = [ 0.00001, 0.00005, 0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, @@ -68,12 +69,12 @@ export class RandomSampler { private setRandomSamplerModeInStorage: (mode: RandomSamplerOption) => void; private setRandomSamplerProbabilityInStorage: (prob: RandomSamplerProbability) => void; - constructor( - randomSamplerMode: RandomSamplerOption, - setRandomSamplerMode: (mode: RandomSamplerOption) => void, - randomSamplerProbability: RandomSamplerProbability, - setRandomSamplerProbability: (prob: RandomSamplerProbability) => void - ) { + constructor({ + randomSamplerMode, + randomSamplerProbability, + setRandomSamplerMode, + setRandomSamplerProbability, + }: RandomSamplerStorage) { this.mode$.next(randomSamplerMode); this.setRandomSamplerModeInStorage = setRandomSamplerMode; this.probability$.next(randomSamplerProbability); diff --git a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_panel.tsx b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_panel.tsx index 1944039a85e7f4..3dab7d6d919da9 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_panel.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_panel.tsx @@ -19,11 +19,19 @@ import { RANDOM_SAMPLER_OPTION, RANDOM_SAMPLER_SELECT_OPTIONS } from './random_s interface Props { randomSampler: RandomSampler; + displayProbability?: boolean; calloutPosition?: 'top' | 'bottom'; + compressed?: boolean; reload: () => void; } -export const SamplingPanel: FC = ({ randomSampler, reload, calloutPosition = 'top' }) => { +export const SamplingPanel: FC = ({ + randomSampler, + reload, + displayProbability = true, + calloutPosition = 'top', + compressed = false, +}) => { const samplingProbability = useObservable( randomSampler.getProbability$(), randomSampler.getProbability() @@ -60,6 +68,7 @@ export const SamplingPanel: FC = ({ randomSampler, reload, calloutPositio ) : null} = ({ randomSampler, reload, calloutPositio } )} helpText={ - randomSamplerPreference === RANDOM_SAMPLER_OPTION.ON_AUTOMATIC ? ( + displayProbability && randomSamplerPreference === RANDOM_SAMPLER_OPTION.ON_AUTOMATIC ? ( ) : null } > ; + +export function useRandomSamplerStorage() { + const [randomSamplerMode, setRandomSamplerMode] = useStorage< + AiOpsKey, + AiOpsStorageMapped + >(AIOPS_RANDOM_SAMPLING_MODE_PREFERENCE, RANDOM_SAMPLER_OPTION.ON_AUTOMATIC); + + const [randomSamplerProbability, setRandomSamplerProbability] = useStorage< + AiOpsKey, + AiOpsStorageMapped + >(AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE, DEFAULT_PROBABILITY); + + return { + randomSamplerMode, + setRandomSamplerMode, + randomSamplerProbability, + setRandomSamplerProbability, + }; +} diff --git a/x-pack/plugins/aiops/public/components/log_categorization/use_categorize_request.ts b/x-pack/plugins/aiops/public/components/log_categorization/use_categorize_request.ts index 6e21ef246329b5..1c792c4f484a66 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/use_categorize_request.ts +++ b/x-pack/plugins/aiops/public/components/log_categorization/use_categorize_request.ts @@ -10,7 +10,6 @@ import { useRef, useCallback, useMemo } from 'react'; import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { isRunningResponse } from '@kbn/data-plugin/public'; -import { useStorage } from '@kbn/ml-local-storage'; import { type CategorizationAdditionalFilter, @@ -21,44 +20,22 @@ import type { CatResponse } from '@kbn/aiops-log-pattern-analysis/types'; import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; -import { - type AiOpsKey, - type AiOpsStorageMapped, - AIOPS_RANDOM_SAMPLING_MODE_PREFERENCE, - AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE, -} from '../../types/storage'; +import type { RandomSamplerStorage } from './sampling_menu'; import { RandomSampler } from './sampling_menu'; -import { RANDOM_SAMPLER_OPTION, DEFAULT_PROBABILITY } from './sampling_menu/random_sampler'; export type EventRate = Array<{ key: number; docCount: number; }>; -export function useCategorizeRequest() { - const [randomSamplerMode, setRandomSamplerMode] = useStorage< - AiOpsKey, - AiOpsStorageMapped - >(AIOPS_RANDOM_SAMPLING_MODE_PREFERENCE, RANDOM_SAMPLER_OPTION.ON_AUTOMATIC); - - const [randomSamplerProbability, setRandomSamplerProbability] = useStorage< - AiOpsKey, - AiOpsStorageMapped - >(AIOPS_RANDOM_SAMPLING_PROBABILITY_PREFERENCE, DEFAULT_PROBABILITY); - +export function useCategorizeRequest(randomSamplerStorage: RandomSamplerStorage) { const { data } = useAiopsAppContext(); const abortController = useRef(new AbortController()); const randomSampler = useMemo( - () => - new RandomSampler( - randomSamplerMode, - setRandomSamplerMode, - randomSamplerProbability, - setRandomSamplerProbability - ), + () => new RandomSampler(randomSamplerStorage), // eslint-disable-next-line react-hooks/exhaustive-deps [] ); diff --git a/x-pack/plugins/aiops/public/embeddables/index.ts b/x-pack/plugins/aiops/public/embeddables/index.ts index 2affeffea77992..b7d9ad25951fb9 100644 --- a/x-pack/plugins/aiops/public/embeddables/index.ts +++ b/x-pack/plugins/aiops/public/embeddables/index.ts @@ -8,6 +8,7 @@ import type { CoreSetup } from '@kbn/core-lifecycle-browser'; import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants'; +import { EMBEDDABLE_PATTERN_ANALYSIS_TYPE } from '@kbn/aiops-log-pattern-analysis/constants'; import type { AiopsPluginStart, AiopsPluginStartDeps } from '../types'; export const registerEmbeddables = ( @@ -18,4 +19,8 @@ export const registerEmbeddables = ( const { getChangePointChartEmbeddableFactory } = await import('./change_point_chart'); return getChangePointChartEmbeddableFactory(core.getStartServices); }); + embeddable.registerReactEmbeddableFactory(EMBEDDABLE_PATTERN_ANALYSIS_TYPE, async () => { + const { getPatternAnalysisEmbeddableFactory } = await import('./pattern_analysis'); + return getPatternAnalysisEmbeddableFactory(core.getStartServices); + }); }; diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx new file mode 100644 index 00000000000000..e1af6b45ac3f9a --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/embeddable_pattern_analysis_factory.tsx @@ -0,0 +1,265 @@ +/* + * 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 { + EMBEDDABLE_PATTERN_ANALYSIS_TYPE, + PATTERN_ANALYSIS_DATA_VIEW_REF_NAME, +} from '@kbn/aiops-log-pattern-analysis/constants'; +import type { Reference } from '@kbn/content-management-utils'; +import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser'; +import { type DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; +import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { + apiHasExecutionContext, + fetch$, + initializeTimeRange, + initializeTitles, + useBatchedPublishingSubjects, +} from '@kbn/presentation-publishing'; +import fastIsEqual from 'fast-deep-equal'; +import { cloneDeep } from 'lodash'; +import React, { useMemo } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { BehaviorSubject, distinctUntilChanged, map, skipWhile } from 'rxjs'; +import { getPatternAnalysisComponent } from '../../shared_components'; +import type { AiopsPluginStart, AiopsPluginStartDeps } from '../../types'; +import { initializePatternAnalysisControls } from './initialize_pattern_analysis_controls'; +import type { + PatternAnalysisEmbeddableApi, + PatternAnalysisEmbeddableRuntimeState, + PatternAnalysisEmbeddableState, +} from './types'; + +export interface EmbeddablePatternAnalysisStartServices { + data: DataPublicPluginStart; +} + +export type EmbeddablePatternAnalysisType = typeof EMBEDDABLE_PATTERN_ANALYSIS_TYPE; + +export const getDependencies = async ( + getStartServices: StartServicesAccessor +) => { + const [ + { http, uiSettings, notifications, ...startServices }, + { lens, data, usageCollection, fieldFormats }, + ] = await getStartServices(); + + return { + http, + uiSettings, + data, + notifications, + lens, + usageCollection, + fieldFormats, + ...startServices, + }; +}; + +export const getPatternAnalysisEmbeddableFactory = ( + getStartServices: StartServicesAccessor +) => { + const factory: ReactEmbeddableFactory< + PatternAnalysisEmbeddableState, + PatternAnalysisEmbeddableRuntimeState, + PatternAnalysisEmbeddableApi + > = { + type: EMBEDDABLE_PATTERN_ANALYSIS_TYPE, + deserializeState: (state) => { + const serializedState = cloneDeep(state.rawState); + // inject the reference + const dataViewIdRef = state.references?.find( + (ref) => ref.name === PATTERN_ANALYSIS_DATA_VIEW_REF_NAME + ); + // if the serializedState already contains a dataViewId, we don't want to overwrite it. (Unsaved state can cause this) + if (dataViewIdRef && serializedState && !serializedState.dataViewId) { + serializedState.dataViewId = dataViewIdRef?.id; + } + return serializedState; + }, + buildEmbeddable: async (state, buildApi, uuid, parentApi) => { + const [coreStart, pluginStart] = await getStartServices(); + + const { http, uiSettings, notifications, ...startServices } = coreStart; + const { lens, data, usageCollection, fieldFormats } = pluginStart; + + const deps = { + http, + uiSettings, + data, + notifications, + lens, + usageCollection, + fieldFormats, + ...startServices, + }; + + const { + api: timeRangeApi, + comparators: timeRangeComparators, + serialize: serializeTimeRange, + } = initializeTimeRange(state); + + const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state); + + const { + patternAnalysisControlsApi, + serializePatternAnalysisChartState, + patternAnalysisControlsComparators, + } = initializePatternAnalysisControls(state); + + const dataLoading = new BehaviorSubject(true); + const blockingError = new BehaviorSubject(undefined); + + const dataViews$ = new BehaviorSubject([ + await deps.data.dataViews.get( + state.dataViewId ?? (await deps.data.dataViews.getDefaultId()) + ), + ]); + + const api = buildApi( + { + ...timeRangeApi, + ...titlesApi, + ...patternAnalysisControlsApi, + getTypeDisplayName: () => + i18n.translate('xpack.aiops.patternAnalysis.typeDisplayName', { + defaultMessage: 'pattern analysis', + }), + isEditingEnabled: () => true, + onEdit: async () => { + try { + const { resolveEmbeddablePatternAnalysisUserInput } = await import( + './resolve_pattern_analysis_config_input' + ); + + const result = await resolveEmbeddablePatternAnalysisUserInput( + coreStart, + pluginStart, + parentApi, + uuid, + false, + patternAnalysisControlsApi, + undefined, + serializePatternAnalysisChartState() + ); + + patternAnalysisControlsApi.updateUserInput(result); + } catch (e) { + return Promise.reject(); + } + }, + dataLoading, + blockingError, + dataViews: dataViews$, + serializeState: () => { + const dataViewId = patternAnalysisControlsApi.dataViewId.getValue(); + const references: Reference[] = dataViewId + ? [ + { + type: DATA_VIEW_SAVED_OBJECT_TYPE, + name: PATTERN_ANALYSIS_DATA_VIEW_REF_NAME, + id: dataViewId, + }, + ] + : []; + return { + rawState: { + timeRange: undefined, + ...serializeTitles(), + ...serializeTimeRange(), + ...serializePatternAnalysisChartState(), + }, + references, + }; + }, + }, + { + ...timeRangeComparators, + ...titleComparators, + ...patternAnalysisControlsComparators, + } + ); + + const PatternAnalysisComponent = getPatternAnalysisComponent(coreStart, pluginStart); + + const onLoading = (v: boolean) => dataLoading.next(v); + const onRenderComplete = () => dataLoading.next(false); + const onError = (error: Error) => blockingError.next(error); + + return { + api, + Component: () => { + if (!apiHasExecutionContext(parentApi)) { + throw new Error('Parent API does not have execution context'); + } + + const [ + dataViewId, + fieldName, + minimumTimeRangeOption, + randomSamplerMode, + randomSamplerProbability, + ] = useBatchedPublishingSubjects( + api.dataViewId, + api.fieldName, + api.minimumTimeRangeOption, + api.randomSamplerMode, + api.randomSamplerProbability + ); + + const reload$ = useMemo( + () => + fetch$(api).pipe( + skipWhile((fetchContext) => !fetchContext.isReload), + map((fetchContext) => Date.now()) + ), + [] + ); + + const timeRange$ = useMemo( + () => + fetch$(api).pipe( + map((fetchContext) => fetchContext.timeRange), + distinctUntilChanged(fastIsEqual) + ), + [] + ); + + const lastReloadRequestTime = useObservable(reload$, Date.now()); + const timeRange = useObservable(timeRange$, undefined); + + let embeddingOrigin; + if (apiHasExecutionContext(parentApi)) { + embeddingOrigin = parentApi.executionContext.type; + } + + return ( + + ); + }, + }; + }, + }; + + return factory; +}; diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/index.ts b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/index.ts new file mode 100644 index 00000000000000..419fcf091f5fd3 --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/index.ts @@ -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 { getPatternAnalysisEmbeddableFactory } from './embeddable_pattern_analysis_factory'; diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/initialize_pattern_analysis_controls.ts b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/initialize_pattern_analysis_controls.ts new file mode 100644 index 00000000000000..31de22365df7f7 --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/initialize_pattern_analysis_controls.ts @@ -0,0 +1,66 @@ +/* + * 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 { StateComparators } from '@kbn/presentation-publishing'; +import { BehaviorSubject } from 'rxjs'; +import type { PatternAnalysisComponentApi, PatternAnalysisEmbeddableState } from './types'; + +type PatternAnalysisEmbeddableCustomState = Omit< + PatternAnalysisEmbeddableState, + 'timeRange' | 'title' | 'description' | 'hidePanelTitles' +>; + +export const initializePatternAnalysisControls = (rawState: PatternAnalysisEmbeddableState) => { + const dataViewId = new BehaviorSubject(rawState.dataViewId); + const fieldName = new BehaviorSubject(rawState.fieldName); + const minimumTimeRangeOption = new BehaviorSubject(rawState.minimumTimeRangeOption); + const randomSamplerMode = new BehaviorSubject(rawState.randomSamplerMode); + const randomSamplerProbability = new BehaviorSubject(rawState.randomSamplerProbability); + + const updateUserInput = (update: PatternAnalysisEmbeddableCustomState) => { + dataViewId.next(update.dataViewId); + fieldName.next(update.fieldName); + minimumTimeRangeOption.next(update.minimumTimeRangeOption); + randomSamplerMode.next(update.randomSamplerMode); + randomSamplerProbability.next(update.randomSamplerProbability); + }; + + const serializePatternAnalysisChartState = (): PatternAnalysisEmbeddableCustomState => { + return { + dataViewId: dataViewId.getValue(), + fieldName: fieldName.getValue(), + minimumTimeRangeOption: minimumTimeRangeOption.getValue(), + randomSamplerMode: randomSamplerMode.getValue(), + randomSamplerProbability: randomSamplerProbability.getValue(), + }; + }; + + const patternAnalysisControlsComparators: StateComparators = + { + dataViewId: [dataViewId, (arg) => dataViewId.next(arg)], + fieldName: [fieldName, (arg) => fieldName.next(arg)], + minimumTimeRangeOption: [minimumTimeRangeOption, (arg) => minimumTimeRangeOption.next(arg)], + randomSamplerMode: [randomSamplerMode, (arg) => randomSamplerMode.next(arg)], + randomSamplerProbability: [ + randomSamplerProbability, + (arg) => randomSamplerProbability.next(arg), + ], + }; + + return { + patternAnalysisControlsApi: { + dataViewId, + fieldName, + minimumTimeRangeOption, + randomSamplerMode, + randomSamplerProbability, + updateUserInput, + } as unknown as PatternAnalysisComponentApi, + serializePatternAnalysisChartState, + patternAnalysisControlsComparators, + }; +}; diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_initializer.tsx new file mode 100644 index 00000000000000..4ed1dadf3dc1ed --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysis_initializer.tsx @@ -0,0 +1,420 @@ +/* + * 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 { + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiForm, + EuiFormRow, + EuiSpacer, + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutFooter, + EuiCallOut, +} from '@elastic/eui'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { i18n } from '@kbn/i18n'; +import type { FC } from 'react'; +import React, { useEffect, useMemo, useState, useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { pick } from 'lodash'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import useObservable from 'react-use/lib/useObservable'; +import type { DataViewField } from '@kbn/data-views-plugin/public'; +import useMountedState from 'react-use/lib/useMountedState'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { DataSourceContextProvider } from '../../hooks/use_data_source'; +import type { PatternAnalysisEmbeddableRuntimeState } from './types'; +import { PatternAnalysisSettings } from '../../components/log_categorization/log_categorization_for_embeddable/embeddable_menu'; +import { RandomSampler } from '../../components/log_categorization/sampling_menu'; +import { + DEFAULT_PROBABILITY, + RANDOM_SAMPLER_OPTION, +} from '../../components/log_categorization/sampling_menu/random_sampler'; +import type { MinimumTimeRangeOption } from '../../components/log_categorization/log_categorization_for_embeddable/minimum_time_range'; +import { getMessageField } from '../../components/log_categorization/utils'; +import { FieldSelector } from '../../components/log_categorization/log_categorization_for_embeddable/field_selector'; +import { SamplingPanel } from '../../components/log_categorization/sampling_menu/sampling_panel'; + +export interface PatternAnalysisInitializerProps { + initialInput?: Partial; + onCreate: (props: PatternAnalysisEmbeddableRuntimeState) => void; + onCancel: () => void; + onPreview: (update: PatternAnalysisEmbeddableRuntimeState) => Promise; + isNewPanel: boolean; +} + +export const PatternAnalysisEmbeddableInitializer: FC = ({ + initialInput, + onCreate, + onCancel, + onPreview, + isNewPanel, +}) => { + const { + unifiedSearch: { + ui: { IndexPatternSelect }, + }, + } = useAiopsAppContext(); + + const [formInput, setFormInput] = useState( + pick( + initialInput ?? { + minimumTimeRangeOption: '1 week', + randomSamplerMode: RANDOM_SAMPLER_OPTION.ON_AUTOMATIC, + randomSamplerProbability: DEFAULT_PROBABILITY, + }, + [ + 'dataViewId', + 'fieldName', + 'minimumTimeRangeOption', + 'randomSamplerMode', + 'randomSamplerProbability', + ] + ) as PatternAnalysisEmbeddableRuntimeState + ); + const [isFormValid, setIsFormValid] = useState(true); + + const updatedProps = useMemo(() => { + return { + ...formInput, + title: isPopulatedObject(formInput) + ? i18n.translate('xpack.aiops.embeddablePatternAnalysis.attachmentTitle', { + defaultMessage: 'Pattern analysis: {fieldName}', + values: { + fieldName: formInput.fieldName, + }, + }) + : '', + }; + }, [formInput]); + + useEffect( + function previewChanges() { + if (isFormValid && updatedProps.fieldName !== undefined) { + onPreview(updatedProps); + } + }, + [isFormValid, onPreview, updatedProps] + ); + + const setDataViewId = useCallback( + (dataViewId: string | undefined) => { + setFormInput({ + ...formInput, + dataViewId: dataViewId ?? '', + fieldName: undefined, + }); + setIsFormValid(false); + }, + [formInput] + ); + + return ( + <> + + +

+ {isNewPanel + ? i18n.translate('xpack.aiops.embeddablePatternAnalysis.config.title.new', { + defaultMessage: 'Create pattern analysis', + }) + : i18n.translate('xpack.aiops.embeddablePatternAnalysis.config.title.edit', { + defaultMessage: 'Edit pattern analysis', + })} +

+
+
+ + + + + { + setDataViewId(newId ?? ''); + }} + /> + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const FormControls: FC<{ + formInput: PatternAnalysisEmbeddableRuntimeState; + onChange: (update: PatternAnalysisEmbeddableRuntimeState) => void; + onValidationChange: (isValid: boolean) => void; +}> = ({ formInput, onChange, onValidationChange }) => { + const dataViewId = formInput.dataViewId; + const { + data: { dataViews }, + } = useAiopsAppContext(); + const [fields, setFields] = useState([]); + const [selectedField, setSelectedField] = useState(null); + const [isDataViewTimeBased, setIsDataViewTimeBased] = useState(true); + + const randomSampler = useMemo(() => { + return new RandomSampler({ + randomSamplerMode: formInput.randomSamplerMode ?? RANDOM_SAMPLER_OPTION.ON_AUTOMATIC, + setRandomSamplerMode: () => {}, + randomSamplerProbability: formInput.randomSamplerProbability ?? DEFAULT_PROBABILITY, + setRandomSamplerProbability: () => {}, + }); + }, [formInput.randomSamplerMode, formInput.randomSamplerProbability]); + const randomSamplerMode = useObservable(randomSampler.getMode$(), randomSampler.getMode()); + const randomSamplerProbability = useObservable( + randomSampler.getProbability$(), + randomSampler.getProbability() + ); + + const isMounted = useMountedState(); + + useEffect( + function initFields() { + if (!dataViewId) { + setFields([]); + setSelectedField(null); + return; + } + + dataViews + .get(dataViewId) + .then((dataView) => { + if (!isMounted()) { + return; + } + const isTimeBased = dataView.isTimeBased(); + setIsDataViewTimeBased(isTimeBased); + if (isTimeBased === false) { + setFields([]); + setSelectedField(null); + return; + } + const { dataViewFields, messageField } = getMessageField(dataView); + setFields(dataViewFields); + const field = dataViewFields.find((f) => f.name === formInput.fieldName); + if (formInput.fieldName === undefined) { + // form input does not contain a field name, select the found message field + setSelectedField(messageField ?? null); + return; + } + // otherwise, select the field from the form input + setSelectedField(field ?? messageField ?? null); + }) + .catch(() => { + setFields([]); + setSelectedField(null); + }); + }, + [dataViewId, dataViews, formInput, isMounted, onChange] + ); + + useEffect( + function validateForm() { + onValidationChange(selectedField !== null && formInput.dataViewId !== undefined); + }, + [selectedField, formInput, onValidationChange] + ); + + useEffect( + function samplerChange() { + onChange({ + ...formInput, + randomSamplerMode, + randomSamplerProbability, + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [onChange, randomSamplerMode, randomSamplerProbability] + ); + + useEffect( + function samplerChange() { + if (selectedField === null) { + return; + } + + onChange({ + ...formInput, + fieldName: selectedField.name, + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [onChange, selectedField] + ); + + const setMinimumTimeRangeOption = (option: MinimumTimeRangeOption) => { + onChange({ + ...formInput, + minimumTimeRangeOption: option, + }); + }; + + return ( + <> + + + + + + + + + {}} + calloutPosition="bottom" + displayProbability={false} + compressed={true} + /> + + ); +}; + +const TextFieldWarning = () => { + return ( + <> + +

+ {i18n.translate( + 'xpack.aiops.logCategorization.embeddableMenu.textFieldWarning.title.description', + { + defaultMessage: 'Pattern analysis can only be run on data views with a text field.', + } + )} +

+
+ + + ); +}; + +const TimeFieldWarning = () => { + return ( + <> + +

+ {i18n.translate( + 'xpack.aiops.logCategorization.embeddableMenu.timeFieldWarning.title.description', + { + defaultMessage: 'Pattern analysis can only be run on data views with a time field.', + } + )} +

+
+ + + ); +}; diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysys_component_wrapper.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysys_component_wrapper.tsx new file mode 100644 index 00000000000000..ebdac78e00c5c9 --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/pattern_analysys_component_wrapper.tsx @@ -0,0 +1,75 @@ +/* + * 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 { FC } from 'react'; +import React from 'react'; +import { useDataSource } from '../../hooks/use_data_source'; +import type { PatternAnalysisProps } from '../../shared_components/pattern_analysis'; +import LogCategorizationEmbeddable from '../../components/log_categorization/log_categorization_for_embeddable/log_categorization_for_embeddable'; + +/** + * Grid component wrapper for embeddable. + * + * @param fieldName + * @param minimumTimeRangeOption + * @param randomSamplerMode + * @param randomSamplerProbability + * @param lastReloadRequestTime + * @param onError + * @param onLoading + * @param onRenderComplete + * @param onChange + * @param emptyState + * @param timeRange + * @constructor + */ +export const PatternAnalysisEmbeddableWrapper: FC = ({ + dataViewId, + fieldName, + minimumTimeRangeOption, + randomSamplerMode, + randomSamplerProbability, + lastReloadRequestTime, + onError, + onLoading, + onRenderComplete, + onChange, + emptyState, + timeRange, +}) => { + const { dataView } = useDataSource(); + + if (dataView.id !== dataViewId) { + return null; + } + + return ( +
+ +
+ ); +}; diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/resolve_pattern_analysis_config_input.tsx b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/resolve_pattern_analysis_config_input.tsx new file mode 100644 index 00000000000000..e1345526bf25d9 --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/resolve_pattern_analysis_config_input.tsx @@ -0,0 +1,103 @@ +/* + * 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 { CoreStart } from '@kbn/core/public'; +import { tracksOverlays } from '@kbn/presentation-containers'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import React from 'react'; +import type { AiopsAppDependencies } from '../..'; +import { AiopsAppContext } from '../../hooks/use_aiops_app_context'; +import type { AiopsPluginStartDeps } from '../../types'; +import { PatternAnalysisEmbeddableInitializer } from './pattern_analysis_initializer'; +import type { PatternAnalysisComponentApi, PatternAnalysisEmbeddableState } from './types'; + +export async function resolveEmbeddablePatternAnalysisUserInput( + coreStart: CoreStart, + pluginStart: AiopsPluginStartDeps, + parentApi: unknown, + focusedPanelId: string, + isNewPanel: boolean, + patternAnalysisControlsApi: PatternAnalysisComponentApi, + deletePanel?: () => void, + initialState?: PatternAnalysisEmbeddableState +): Promise { + const { overlays } = coreStart; + + const overlayTracker = tracksOverlays(parentApi) ? parentApi : undefined; + + let hasChanged = false; + return new Promise(async (resolve, reject) => { + try { + const cancelChanges = () => { + if (isNewPanel && deletePanel) { + deletePanel(); + } else if (hasChanged && patternAnalysisControlsApi && initialState) { + // Reset to initialState in case user has changed the preview state + patternAnalysisControlsApi.updateUserInput(initialState); + } + + flyoutSession.close(); + overlayTracker?.clearOverlays(); + }; + + const update = async (nextUpdate: PatternAnalysisEmbeddableState) => { + resolve(nextUpdate); + flyoutSession.close(); + overlayTracker?.clearOverlays(); + }; + + const preview = async (nextUpdate: PatternAnalysisEmbeddableState) => { + if (patternAnalysisControlsApi) { + patternAnalysisControlsApi.updateUserInput(nextUpdate); + hasChanged = true; + } + }; + + const flyoutSession = overlays.openFlyout( + toMountPoint( + + + , + coreStart + ), + { + ownFocus: true, + size: 's', + type: 'push', + paddingSize: 'm', + hideCloseButton: true, + 'data-test-subj': 'aiopsPatternAnalysisEmbeddableInitializer', + 'aria-labelledby': 'patternAnalysisConfig', + onClose: () => { + reject(); + flyoutSession.close(); + overlayTracker?.clearOverlays(); + }, + } + ); + + if (tracksOverlays(parentApi)) { + parentApi.openOverlay(flyoutSession, { focusedPanelId }); + } + } catch (error) { + reject(error); + } + }); +} diff --git a/x-pack/plugins/aiops/public/embeddables/pattern_analysis/types.ts b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/types.ts new file mode 100644 index 00000000000000..f78934b9075f1f --- /dev/null +++ b/x-pack/plugins/aiops/public/embeddables/pattern_analysis/types.ts @@ -0,0 +1,62 @@ +/* + * 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 { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; +import type { + HasEditCapabilities, + PublishesDataViews, + PublishesTimeRange, + PublishingSubject, + SerializedTimeRange, + SerializedTitles, +} from '@kbn/presentation-publishing'; +import type { FC } from 'react'; +import type { MinimumTimeRangeOption } from '../../components/log_categorization/log_categorization_for_embeddable/minimum_time_range'; +import type { + RandomSamplerOption, + RandomSamplerProbability, +} from '../../components/log_categorization/sampling_menu/random_sampler'; + +export type ViewComponent = FC<{ + interval: string; + onRenderComplete?: () => void; +}>; + +export interface PatternAnalysisComponentApi { + dataViewId: PublishingSubject; + fieldName: PublishingSubject; + minimumTimeRangeOption: PublishingSubject< + PatternAnalysisEmbeddableState['minimumTimeRangeOption'] + >; + randomSamplerMode: PublishingSubject; + randomSamplerProbability: PublishingSubject< + PatternAnalysisEmbeddableState['randomSamplerProbability'] + >; + updateUserInput: (update: PatternAnalysisEmbeddableState) => void; +} + +export type PatternAnalysisEmbeddableApi = DefaultEmbeddableApi & + HasEditCapabilities & + PublishesDataViews & + PublishesTimeRange & + PatternAnalysisComponentApi; + +export interface PatternAnalysisEmbeddableState extends SerializedTitles, SerializedTimeRange { + dataViewId: string; + fieldName: string | undefined; + minimumTimeRangeOption: MinimumTimeRangeOption; + randomSamplerMode: RandomSamplerOption; + randomSamplerProbability: RandomSamplerProbability; +} + +export interface PatternAnalysisEmbeddableInitialState + extends SerializedTitles, + SerializedTimeRange { + dataViewId?: string; +} + +export type PatternAnalysisEmbeddableRuntimeState = PatternAnalysisEmbeddableState; diff --git a/x-pack/plugins/aiops/public/plugin.tsx b/x-pack/plugins/aiops/public/plugin.tsx index 7d55c3098cfafe..ceb378b0f29bfa 100755 --- a/x-pack/plugins/aiops/public/plugin.tsx +++ b/x-pack/plugins/aiops/public/plugin.tsx @@ -72,7 +72,7 @@ export class AiopsPlugin PatternAnalysisComponent: dynamic( async () => import( - './components/log_categorization/log_categorization_for_embeddable/log_categorization_wrapper' + './components/log_categorization/log_categorization_for_embeddable/log_categorization_for_discover_wrapper' ) ), }; diff --git a/x-pack/plugins/aiops/public/shared_components/index.tsx b/x-pack/plugins/aiops/public/shared_components/index.tsx index 3e19cfda3b844a..1c5b85c4b79b6c 100644 --- a/x-pack/plugins/aiops/public/shared_components/index.tsx +++ b/x-pack/plugins/aiops/public/shared_components/index.tsx @@ -10,6 +10,7 @@ import { dynamic } from '@kbn/shared-ux-utility'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; import type { AiopsPluginStartDeps } from '../types'; import type { ChangePointDetectionSharedComponent } from './change_point_detection'; +import type { PatternAnalysisSharedComponent } from './pattern_analysis'; const ChangePointDetectionLazy = dynamic(async () => import('./change_point_detection')); @@ -23,3 +24,16 @@ export const getChangePointDetectionComponent = ( }; export type { ChangePointDetectionSharedComponent } from './change_point_detection'; + +const PatternAnalysisLazy = dynamic(async () => import('./pattern_analysis')); + +export const getPatternAnalysisComponent = ( + coreStart: CoreStart, + pluginStart: AiopsPluginStartDeps +): PatternAnalysisSharedComponent => { + return React.memo((props) => { + return ; + }); +}; + +export type { PatternAnalysisSharedComponent } from './pattern_analysis'; diff --git a/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx b/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx new file mode 100644 index 00000000000000..af474f4f5ef3e8 --- /dev/null +++ b/x-pack/plugins/aiops/public/shared_components/pattern_analysis.tsx @@ -0,0 +1,172 @@ +/* + * 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 { EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants'; +import type { Category } from '@kbn/aiops-log-pattern-analysis/types'; +import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import { UI_SETTINGS } from '@kbn/data-service'; +import type { TimeRange } from '@kbn/es-query'; +import { DatePickerContextProvider } from '@kbn/ml-date-picker'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { pick } from 'lodash'; +import React, { useEffect, useMemo, useState, type FC } from 'react'; +import type { Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, distinctUntilChanged, map } from 'rxjs'; +import type { MinimumTimeRangeOption } from '../components/log_categorization/log_categorization_for_embeddable/minimum_time_range'; +import type { + RandomSamplerOption, + RandomSamplerProbability, +} from '../components/log_categorization/sampling_menu/random_sampler'; +import { PatternAnalysisEmbeddableWrapper } from '../embeddables/pattern_analysis/pattern_analysys_component_wrapper'; +import { AiopsAppContext, type AiopsAppDependencies } from '../hooks/use_aiops_app_context'; +import { DataSourceContextProvider } from '../hooks/use_data_source'; +import { FilterQueryContextProvider } from '../hooks/use_filters_query'; +import { ReloadContextProvider } from '../hooks/use_reload'; +import type { AiopsPluginStartDeps } from '../types'; + +/** + * Only used to initialize internally + */ +export type PatternAnalysisPropsWithDeps = PatternAnalysisProps & { + coreStart: CoreStart; + pluginStart: AiopsPluginStartDeps; +}; + +export type PatternAnalysisSharedComponent = FC; + +export interface PatternAnalysisProps { + dataViewId: string; + timeRange: TimeRange; + fieldName: string | undefined; + minimumTimeRangeOption: MinimumTimeRangeOption; + randomSamplerMode: RandomSamplerOption; + randomSamplerProbability: RandomSamplerProbability; + /** + * Component to render if there are no patterns found + */ + emptyState?: React.ReactElement; + /** + * Outputs the most recent patterns data + */ + onChange?: (patterns: Category[]) => void; + /** + * Last reload request time, can be used for manual reload + */ + lastReloadRequestTime?: number; + /** Origin of the embeddable instance */ + embeddingOrigin?: string; + onLoading: (isLoading: boolean) => void; + onRenderComplete: () => void; + onError: (error: Error) => void; +} + +const PatternAnalysisWrapper: FC = ({ + // Component dependencies + coreStart, + pluginStart, + // Component props + dataViewId, + timeRange, + fieldName, + minimumTimeRangeOption, + randomSamplerMode, + randomSamplerProbability, + onLoading, + onError, + onRenderComplete, + embeddingOrigin, + lastReloadRequestTime, + onChange, +}) => { + const deps = useMemo(() => { + const { http, uiSettings, notifications, ...startServices } = coreStart; + const { lens, data, usageCollection, fieldFormats, charts } = pluginStart; + + return { + http, + uiSettings, + data, + notifications, + lens, + usageCollection, + fieldFormats, + charts, + ...startServices, + }; + }, [coreStart, pluginStart]); + + const datePickerDeps = { + ...pick(deps, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), + uiSettingsKeys: UI_SETTINGS, + }; + + const aiopsAppContextValue = useMemo(() => { + return { + embeddingOrigin: embeddingOrigin ?? EMBEDDABLE_ORIGIN, + ...deps, + } as unknown as AiopsAppDependencies; + }, [deps, embeddingOrigin]); + + const [manualReload$] = useState>( + new BehaviorSubject(lastReloadRequestTime ?? Date.now()) + ); + + useEffect( + function updateManualReloadSubject() { + if (!lastReloadRequestTime) return; + manualReload$.next(lastReloadRequestTime); + }, + [lastReloadRequestTime, manualReload$] + ); + + const resultObservable$ = useMemo>(() => { + return combineLatest([manualReload$]).pipe( + map(([manualReload]) => Math.max(manualReload)), + distinctUntilChanged() + ); + }, [manualReload$]); + + // TODO: Remove data-shared-item as part of https://github.com/elastic/kibana/issues/179376> + return ( +
+ + + + + + + + + + + + + +
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export default PatternAnalysisWrapper; diff --git a/x-pack/plugins/aiops/public/types/index.ts b/x-pack/plugins/aiops/public/types/index.ts index 9150c9b983f84e..561c91b178eac7 100644 --- a/x-pack/plugins/aiops/public/types/index.ts +++ b/x-pack/plugins/aiops/public/types/index.ts @@ -22,7 +22,7 @@ import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { ChangePointDetectionSharedComponent } from '../shared_components'; -import type { LogCategorizationEmbeddableWrapperProps } from '../components/log_categorization/log_categorization_for_embeddable/log_categorization_wrapper'; +import type { LogCategorizationEmbeddableWrapperProps } from '../components/log_categorization/log_categorization_for_embeddable/log_categorization_for_discover_wrapper'; export interface AiopsPluginSetupDeps { embeddable: EmbeddableSetup; diff --git a/x-pack/plugins/aiops/public/ui_actions/create_pattern_analysis_action.tsx b/x-pack/plugins/aiops/public/ui_actions/create_pattern_analysis_action.tsx new file mode 100644 index 00000000000000..81127559e4e3db --- /dev/null +++ b/x-pack/plugins/aiops/public/ui_actions/create_pattern_analysis_action.tsx @@ -0,0 +1,97 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import type { PresentationContainer } from '@kbn/presentation-containers'; +import type { EmbeddableApiContext } from '@kbn/presentation-publishing'; +import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public'; +import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import { EMBEDDABLE_PATTERN_ANALYSIS_TYPE } from '@kbn/aiops-log-pattern-analysis/constants'; +import type { AiopsPluginStartDeps } from '../types'; +import type { PatternAnalysisActionContext } from './pattern_analysis_action_context'; +import type { + PatternAnalysisEmbeddableApi, + PatternAnalysisEmbeddableInitialState, +} from '../embeddables/pattern_analysis/types'; + +const parentApiIsCompatible = async ( + parentApi: unknown +): Promise => { + const { apiIsPresentationContainer } = await import('@kbn/presentation-containers'); + // we cannot have an async type check, so return the casted parentApi rather than a boolean + return apiIsPresentationContainer(parentApi) ? (parentApi as PresentationContainer) : undefined; +}; + +export function createAddPatternAnalysisEmbeddableAction( + coreStart: CoreStart, + pluginStart: AiopsPluginStartDeps +): UiActionsActionDefinition { + return { + id: 'create-pattern-analysis-embeddable', + grouping: [ + { + id: 'ml', + getDisplayName: () => + i18n.translate('xpack.aiops.navMenu.mlAppNameText', { + defaultMessage: 'Machine Learning and Analytics', + }), + getIconType: () => 'logPatternAnalysis', + }, + ], + getIconType: () => 'logPatternAnalysis', + getDisplayName: () => + i18n.translate('xpack.aiops.embeddablePatternAnalysisDisplayName', { + defaultMessage: 'Pattern analysis', + }), + async isCompatible(context: EmbeddableApiContext) { + return Boolean(await parentApiIsCompatible(context.embeddable)); + }, + async execute(context) { + const presentationContainerParent = await parentApiIsCompatible(context.embeddable); + if (!presentationContainerParent) throw new IncompatibleActionError(); + + try { + const { resolveEmbeddablePatternAnalysisUserInput } = await import( + '../embeddables/pattern_analysis/resolve_pattern_analysis_config_input' + ); + + const initialState: PatternAnalysisEmbeddableInitialState = { + dataViewId: undefined, + }; + + const embeddable = await presentationContainerParent.addNewPanel< + object, + PatternAnalysisEmbeddableApi + >({ + panelType: EMBEDDABLE_PATTERN_ANALYSIS_TYPE, + initialState, + }); + + if (!embeddable) { + return; + } + + const deletePanel = () => { + presentationContainerParent.removePanel(embeddable.uuid); + }; + + resolveEmbeddablePatternAnalysisUserInput( + coreStart, + pluginStart, + context.embeddable, + embeddable.uuid, + true, + embeddable, + deletePanel + ); + } catch (e) { + return Promise.reject(); + } + }, + }; +} diff --git a/x-pack/plugins/aiops/public/ui_actions/index.ts b/x-pack/plugins/aiops/public/ui_actions/index.ts index c8e4edb58792b3..d14856fd287335 100644 --- a/x-pack/plugins/aiops/public/ui_actions/index.ts +++ b/x-pack/plugins/aiops/public/ui_actions/index.ts @@ -17,6 +17,7 @@ import { createAddChangePointChartAction } from './create_change_point_chart'; import { createOpenChangePointInMlAppAction } from './open_change_point_ml'; import type { AiopsPluginStartDeps } from '../types'; import { createCategorizeFieldAction } from '../components/log_categorization'; +import { createAddPatternAnalysisEmbeddableAction } from './create_pattern_analysis_action'; export function registerAiopsUiActions( uiActions: UiActionsSetup, @@ -25,7 +26,9 @@ export function registerAiopsUiActions( ) { const openChangePointInMlAppAction = createOpenChangePointInMlAppAction(coreStart, pluginStart); const addChangePointChartAction = createAddChangePointChartAction(coreStart, pluginStart); + const addPatternAnalysisAction = createAddPatternAnalysisEmbeddableAction(coreStart, pluginStart); + uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addPatternAnalysisAction); uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addChangePointChartAction); uiActions.registerTrigger(categorizeFieldTrigger); diff --git a/x-pack/plugins/aiops/public/ui_actions/pattern_analysis_action_context.ts b/x-pack/plugins/aiops/public/ui_actions/pattern_analysis_action_context.ts new file mode 100644 index 00000000000000..d2ed27ae7dfd24 --- /dev/null +++ b/x-pack/plugins/aiops/public/ui_actions/pattern_analysis_action_context.ts @@ -0,0 +1,24 @@ +/* + * 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 { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { apiIsOfType, type EmbeddableApiContext } from '@kbn/presentation-publishing'; +import { EMBEDDABLE_PATTERN_ANALYSIS_TYPE } from '@kbn/aiops-log-pattern-analysis/constants'; +import type { ChangePointEmbeddableApi } from '../embeddables/change_point_chart/types'; + +export interface PatternAnalysisActionContext extends EmbeddableApiContext { + embeddable: ChangePointEmbeddableApi; +} + +export function isPatternAnalysisEmbeddableContext( + arg: unknown +): arg is PatternAnalysisActionContext { + return ( + isPopulatedObject(arg, ['embeddable']) && + apiIsOfType(arg.embeddable, EMBEDDABLE_PATTERN_ANALYSIS_TYPE) + ); +} diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index 1c89006ff5eff6..1b0e742521ddc1 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -78,6 +78,7 @@ "@kbn/usage-collection-plugin", "@kbn/utility-types", "@kbn/observability-ai-assistant-plugin", + "@kbn/ui-theme", "@kbn/apm-utils", ], "exclude": [ diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts index 3a1838342c5d69..c63987bea9428e 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts @@ -236,8 +236,8 @@ export class LegacyAlertsClient< return {}; } - public getAlertsToSerialize(shouldSetFlapping: boolean = true) { - if (shouldSetFlapping) { + public getAlertsToSerialize(shouldSetFlappingAndOptimize: boolean = true) { + if (shouldSetFlappingAndOptimize) { setFlapping( this.flappingSettings, this.processedAlerts.active, @@ -246,7 +246,8 @@ export class LegacyAlertsClient< } return determineAlertsToReturn( this.processedAlerts.active, - this.processedAlerts.recovered + this.processedAlerts.recovered, + shouldSetFlappingAndOptimize ); } diff --git a/x-pack/plugins/alerting/server/lib/determine_alerts_to_return.test.ts b/x-pack/plugins/alerting/server/lib/determine_alerts_to_return.test.ts index 4fd80033fd3414..28ce69b6ca8cf9 100644 --- a/x-pack/plugins/alerting/server/lib/determine_alerts_to_return.test.ts +++ b/x-pack/plugins/alerting/server/lib/determine_alerts_to_return.test.ts @@ -31,5 +31,14 @@ describe('determineAlertsToReturn', () => { const { recoveredAlertsToReturn } = determineAlertsToReturn({}, recoveredAlerts); expect(keys(recoveredAlertsToReturn)).toEqual(['1']); }); + + test('should return all recovered alerts if the optimization flag is set to false', () => { + const recoveredAlerts = { + '1': new Alert('1', { meta: { flappingHistory: flapping } }), + '2': new Alert('2', { meta: { flappingHistory: notFlapping } }), + }; + const { recoveredAlertsToReturn } = determineAlertsToReturn({}, recoveredAlerts, false); + expect(keys(recoveredAlertsToReturn)).toEqual(['1', '2']); + }); }); }); diff --git a/x-pack/plugins/alerting/server/lib/determine_alerts_to_return.ts b/x-pack/plugins/alerting/server/lib/determine_alerts_to_return.ts index 5916bf91efcc04..2449c48ca83f78 100644 --- a/x-pack/plugins/alerting/server/lib/determine_alerts_to_return.ts +++ b/x-pack/plugins/alerting/server/lib/determine_alerts_to_return.ts @@ -17,7 +17,8 @@ export function determineAlertsToReturn< RecoveryActionGroupId extends string >( activeAlerts: Record> = {}, - recoveredAlerts: Record> = {} + recoveredAlerts: Record> = {}, + shouldOptimizeTaskState: boolean = true ): { alertsToReturn: Record; recoveredAlertsToReturn: Record; @@ -32,15 +33,19 @@ export function determineAlertsToReturn< for (const id of keys(recoveredAlerts)) { const alert = recoveredAlerts[id]; - // return recovered alerts if they are flapping or if the flapping array is not at capacity - // this is a space saving effort that will stop tracking a recovered alert if it wasn't flapping and doesn't have state changes - // in the last max capcity number of executions - const flapping = alert.getFlapping(); - const flappingHistory: boolean[] = alert.getFlappingHistory() || []; - const numStateChanges = flappingHistory.filter((f) => f).length; - if (flapping) { - recoveredAlertsToReturn[id] = alert.toRaw(true); - } else if (numStateChanges > 0) { + if (shouldOptimizeTaskState) { + // return recovered alerts if they are flapping or if the flapping array is not at capacity + // this is a space saving effort that will stop tracking a recovered alert if it wasn't flapping and doesn't have state changes + // in the last max capcity number of executions + const flapping = alert.getFlapping(); + const flappingHistory: boolean[] = alert.getFlappingHistory() || []; + const numStateChanges = flappingHistory.filter((f) => f).length; + if (flapping) { + recoveredAlertsToReturn[id] = alert.toRaw(true); + } else if (numStateChanges > 0) { + recoveredAlertsToReturn[id] = alert.toRaw(true); + } + } else { recoveredAlertsToReturn[id] = alert.toRaw(true); } } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx index 237ab76465fdd0..23a7ae4cc2fe4c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx @@ -83,6 +83,8 @@ describe('step select agent policy', () => { beforeEach(() => { testRenderer = createFleetTestRendererMock(); useMultipleAgentPoliciesMock.mockReturnValue({ canUseMultipleAgentPolicies: false }); + }); + afterEach(() => { updateAgentPoliciesMock.mockReset(); }); @@ -110,7 +112,7 @@ describe('step select agent policy', () => { }); test('should select agent policy by default if one exists', async () => { - useGetAgentPoliciesMock.mockReturnValueOnce({ + useGetAgentPoliciesMock.mockReturnValue({ data: { items: [{ id: 'policy-1', name: 'Policy 1' }] }, error: undefined, isLoading: false, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/grid.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/grid.tsx index 0c495b2610f7f3..2ce70002627c9e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/grid.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/grid.tsx @@ -7,12 +7,18 @@ import React, { useCallback, useRef, useEffect, forwardRef } from 'react'; import { css } from '@emotion/react'; -import { EuiFlexGrid, EuiFlexItem, EuiSpacer, EuiText, EuiAutoSizer } from '@elastic/eui'; +import { + EuiFlexGrid, + EuiFlexItem, + EuiSpacer, + EuiText, + EuiAutoSizer, + EuiSkeletonRectangle, +} from '@elastic/eui'; import { VariableSizeList as List } from 'react-window'; import { FormattedMessage } from '@kbn/i18n-react'; import { WindowScroller } from 'react-virtualized'; -import { Loading } from '../../../../components'; import type { IntegrationCardItem } from '../../screens/home'; import { PackageCard } from '../package_card'; @@ -66,7 +72,17 @@ export const GridColumn = ({ } }, []); - if (isLoading) return ; + if (isLoading) { + return ( + + {Array.from({ length: 12 }).map((_, index) => ( + + + + ))} + + ); + } if (!list.length) { return ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx index 031a972e308517..1b7a1152ef3ccc 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFacetButton, EuiFacetGroup } from '@elastic/eui'; +import { EuiFacetButton, EuiFacetGroup, EuiSpacer } from '@elastic/eui'; import type { IntegrationCategory } from '@kbn/custom-integrations-plugin/common'; import React from 'react'; @@ -78,7 +78,11 @@ export function CategoryFacets({ const controls = ( {isLoading ? ( - + <> + + + + ) : ( categories.map((category) => { return ( diff --git a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts index 1ea668fd8955cd..c7d40e84abdd01 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts @@ -54,13 +54,16 @@ export function useGetReplacementCustomIntegrationsQuery() { } export function useGetCategoriesQuery(query: GetCategoriesRequest['query'] = {}) { - return useQuery(['categories', query], () => - sendRequestForRq({ - path: epmRouteService.getCategoriesPath(), - method: 'get', - query, - version: API_VERSIONS.public.v1, - }) + return useQuery( + ['categories', query], + () => + sendRequestForRq({ + path: epmRouteService.getCategoriesPath(), + method: 'get', + query, + version: API_VERSIONS.public.v1, + }), + { retry: (_, error) => !isRegistryConnectionError(error), refetchOnWindowFocus: false } ); } @@ -96,6 +99,8 @@ export const useGetPackagesQuery = ( query, }), enabled: options?.enabled, + retry: (_, error) => !isRegistryConnectionError(error), + refetchOnWindowFocus: false, }); }; @@ -151,7 +156,12 @@ export const useGetPackageInfoByKeyQuery = ( ...(ignoreUnverifiedQueryParam && { ignoreUnverified: ignoreUnverifiedQueryParam }), }, }), - { enabled: queryOptions.enabled, refetchOnMount: queryOptions.refetchOnMount } + { + enabled: queryOptions.enabled, + refetchOnMount: queryOptions.refetchOnMount, + retry: (_, error) => !isRegistryConnectionError(error), + refetchOnWindowFocus: false, + } ); const confirm = async () => { @@ -360,3 +370,7 @@ export function useGetInputsTemplatesQuery( }) ); } + +function isRegistryConnectionError(error: RequestError) { + return error.statusCode === 502; +} diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx index c4d366daa1d292..76b8295b6b1f4d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx @@ -731,7 +731,7 @@ describe('', () => { httpRequestsMockHelpers.setInferenceModels({ data: [ { - model_id: customInferenceModel, + inference_id: customInferenceModel, task_type: 'sparse_embedding', service: 'elser', service_settings: { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx index 75afa8c9455b21..ed5af678405132 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx @@ -71,9 +71,9 @@ jest.mock('../../../public/application/components/mappings_editor/mappings_state jest.mock('../../../public/application/services/api', () => ({ useLoadInferenceEndpoints: jest.fn().mockReturnValue({ data: [ - { model_id: 'endpoint-1', task_type: 'text_embedding' }, - { model_id: 'endpoint-2', task_type: 'sparse_embedding' }, - { model_id: 'endpoint-3', task_type: 'completion' }, + { inference_id: 'endpoint-1', task_type: 'text_embedding' }, + { inference_id: 'endpoint-2', task_type: 'sparse_embedding' }, + { inference_id: 'endpoint-3', task_type: 'completion' }, ] as InferenceAPIConfigResponse[], isLoading: false, error: null, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx index 672d64766e5763..15d5ee9ba72c05 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx @@ -54,10 +54,10 @@ type SelectInferenceIdContentProps = SelectInferenceIdProps & { const defaultEndpoints = [ { - model_id: 'elser_model_2', + inference_id: 'elser_model_2', }, { - model_id: 'e5', + inference_id: 'e5', }, ]; @@ -135,15 +135,15 @@ const SelectInferenceIdContent: React.FC = ({ ); const missingDefaultEndpoints = defaultEndpoints.filter( - (endpoint) => !(filteredEndpoints || []).find((e) => e.model_id === endpoint.model_id) + (endpoint) => !(filteredEndpoints || []).find((e) => e.inference_id === endpoint.inference_id) ); const newOptions: EuiSelectableOption[] = [ ...(filteredEndpoints || []), ...missingDefaultEndpoints, ].map((endpoint) => ({ - label: endpoint.model_id, - 'data-test-subj': `custom-inference_${endpoint.model_id}`, - checked: value === endpoint.model_id ? 'on' : undefined, + label: endpoint.inference_id, + 'data-test-subj': `custom-inference_${endpoint.inference_id}`, + checked: value === endpoint.inference_id ? 'on' : undefined, })); if (value && !newOptions.find((option) => option.label === value)) { // Sometimes we create a new endpoint but the backend is slow in updating so we need to optimistically update diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts index 72e007b86f7868..c4e668f9635d1d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts @@ -142,7 +142,7 @@ jest.mock('../../../../../../../services/api', () => ({ getInferenceEndpoints: jest.fn().mockResolvedValue({ data: [ { - model_id: 'e5', + inference_id: 'e5', task_type: 'text_embedding', service: 'elasticsearch', service_settings: { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts index 5ef20e50cf00c3..57871383149cf3 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts @@ -119,7 +119,7 @@ export function useSemanticText(props: UseSemanticTextProps) { dispatch({ type: 'field.add', value: data }); const inferenceEndpoints = await getInferenceEndpoints(); const hasInferenceEndpoint = inferenceEndpoints.data?.some( - (inference) => inference.model_id === inferenceId + (inference) => inference.inference_id === inferenceId ); // if inference endpoint exists already, do not create new inference endpoint if (hasInferenceEndpoint) { diff --git a/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts b/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts index c808123b14a76f..07cda78a1527eb 100644 --- a/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts +++ b/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts @@ -40,7 +40,7 @@ export const useIndexErrors = ( const semanticTextFieldsWithErrors = semanticTextFields .map((field) => { const model = endpoints.find( - (endpoint) => endpoint.model_id === field.source.inference_id + (endpoint) => endpoint.inference_id === field.source.inference_id ); if (!model) { return { diff --git a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts index 6e80bcea5c349c..62746791510c0d 100644 --- a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts +++ b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts @@ -49,7 +49,7 @@ jest.mock('../application/services/api', () => ({ getInferenceEndpoints: jest.fn().mockResolvedValue({ data: [ { - model_id: 'e5', + inference_id: 'e5', task_type: 'text_embedding', service: 'elasticsearch', service_settings: { diff --git a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts index 79d4c8f79cf338..9ad3e74e96a9ef 100644 --- a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts +++ b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts @@ -45,7 +45,7 @@ const getCustomInferenceIdMap = ( isDownloading: false, modelStats: undefined, }; - inferenceMap[model.model_id] = inferenceEntry; + inferenceMap[model.inference_id] = inferenceEntry; return inferenceMap; }, {}); const defaultInferenceIds = { diff --git a/x-pack/plugins/ml/public/application/model_management/delete_models_modal.tsx b/x-pack/plugins/ml/public/application/model_management/delete_models_modal.tsx index 2b48ab3fbb14ef..1e08ae98745674 100644 --- a/x-pack/plugins/ml/public/application/model_management/delete_models_modal.tsx +++ b/x-pack/plugins/ml/public/application/model_management/delete_models_modal.tsx @@ -49,7 +49,7 @@ export const DeleteModelsModal: FC = ({ models, onClose const modelsWithInferenceAPIs = models.filter((m) => m.hasInferenceServices); const inferenceAPIsIDs: string[] = modelsWithInferenceAPIs.flatMap((model) => { - return (model.inference_apis ?? []).map((inference) => inference.model_id); + return (model.inference_apis ?? []).map((inference) => inference.inference_id); }); const pipelinesCount = modelsWithPipelines.reduce((acc, curr) => { diff --git a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx index 171143a581194d..26583623c04f35 100644 --- a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx +++ b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx @@ -41,7 +41,7 @@ export const StopModelDeploymentsConfirmDialog: FC { if (!model.inference_apis) return true; - return !model.inference_apis.some((inference) => inference.model_id === deploymentId); + return !model.inference_apis.some((inference) => inference.inference_id === deploymentId); }) ); }, [model]); @@ -110,7 +110,7 @@ export const StopModelDeploymentsConfirmDialog: FC(() => { - return (model.inference_apis ?? []).map((inference) => inference.model_id); + return (model.inference_apis ?? []).map((inference) => inference.inference_id); }, [model]); return ( diff --git a/x-pack/plugins/ml/public/application/model_management/inference_api_tab.tsx b/x-pack/plugins/ml/public/application/model_management/inference_api_tab.tsx index dba6621a269b5b..dc86c359bb1aa5 100644 --- a/x-pack/plugins/ml/public/application/model_management/inference_api_tab.tsx +++ b/x-pack/plugins/ml/public/application/model_management/inference_api_tab.tsx @@ -28,7 +28,7 @@ export const InferenceApi: FC = ({ inferenceApis }) => { {inferenceApis.map((inferenceApi, i) => { const initialIsOpen = i <= 2; - const modelId = inferenceApi.model_id; + const modelId = inferenceApi.inference_id; return ( diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index 8578be7a573523..9bcf348f451896 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -329,7 +329,7 @@ export function useModelActions({ item.deployment_ids.some( (dId) => Array.isArray(item.inference_apis) && - !item.inference_apis.some((inference) => inference.model_id === dId) + !item.inference_apis.some((inference) => inference.inference_id === dId) )), enabled: (item) => !isLoading, onClick: async (item) => { diff --git a/x-pack/plugins/ml/server/routes/trained_models.test.ts b/x-pack/plugins/ml/server/routes/trained_models.test.ts index ef2087c696a9fb..ca3eb19e757c60 100644 --- a/x-pack/plugins/ml/server/routes/trained_models.test.ts +++ b/x-pack/plugins/ml/server/routes/trained_models.test.ts @@ -33,7 +33,7 @@ describe('populateInferenceServicesProvider', () => { { model_id: 'model2' }, ] as TrainedModelConfigResponse[]; - client.asInternalUser.transport.request.mockResolvedValue({ models: inferenceServices }); + client.asInternalUser.transport.request.mockResolvedValue({ endpoints: inferenceServices }); jest.clearAllMocks(); }); @@ -44,7 +44,7 @@ describe('populateInferenceServicesProvider', () => { describe('when the user has required privileges', () => { beforeEach(() => { - client.asCurrentUser.transport.request.mockResolvedValue({ models: inferenceServices }); + client.asCurrentUser.transport.request.mockResolvedValue({ endpoints: inferenceServices }); }); test('should populate inference services for trained models', async () => { diff --git a/x-pack/plugins/ml/server/routes/trained_models.ts b/x-pack/plugins/ml/server/routes/trained_models.ts index 5b2441435268da..a41967704136d4 100644 --- a/x-pack/plugins/ml/server/routes/trained_models.ts +++ b/x-pack/plugins/ml/server/routes/trained_models.ts @@ -69,16 +69,16 @@ export const populateInferenceServicesProvider = (client: IScopedClusterClient) try { // Check if model is used by an inference service - const { models } = await esClient.transport.request<{ - models: InferenceAPIConfigResponse[]; + const { endpoints } = await esClient.transport.request<{ + endpoints: InferenceAPIConfigResponse[]; }>({ method: 'GET', path: `/_inference/_all`, }); const inferenceAPIMap = groupBy( - models, - (model) => model.service === 'elser' && model.service_settings.model_id + endpoints, + (endpoint) => endpoint.service === 'elser' && endpoint.service_settings.model_id ); for (const model of trainedModels) { diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts b/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts index f6ba40b8b8bcfa..a869d3fa706521 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts @@ -52,12 +52,12 @@ export type IntegrationDashboardsResponse = rt.TypeOf { 'xpack.observability_onboarding.experimentalOnboardingFlow.exploreForumFlexItemLinkLabel', { defaultMessage: 'Discuss forum' } ), + linkARIALabel: i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.exploreForumFlexItemLinkARIALabel', + { defaultMessage: 'Open Elastic Discuss forum' } + ), link: URL_FORUM, }, { @@ -78,6 +82,10 @@ export const Footer: FunctionComponent = () => { 'xpack.observability_onboarding.experimentalOnboardingFlow.browseDocumentationFlexItemLinkLabel', { defaultMessage: 'Learn more' } ), + linkARIALabel: i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.browseDocumentationFlexItemLinkARIALabel', + { defaultMessage: 'Learn more about all Elastic features' } + ), link: docLinks.links.observability.guide, }, { @@ -118,6 +126,7 @@ export const Footer: FunctionComponent = () => {

{ const customMargin = useCustomMargin(); const radioGroupId = useGeneratedHtmlId({ prefix: 'onboardingCategory' }); + const categorySelectorTitleId = useGeneratedHtmlId(); + const packageListTitleId = useGeneratedHtmlId(); const [searchParams, setSearchParams] = useSearchParams(); @@ -138,6 +140,7 @@ export const OnboardingFlowForm: FunctionComponent = () => { return ( { )} /> - + {options.map((option) => ( { - {option.label} - - - {option.description} - - {(option.logos || option.showIntegrationsBadge) && ( - <> - - - {option.logos?.map((logo) => ( - - - - ))} - {option.showIntegrationsBadge && ( - - - - )} - - - )} - - } + label={{option.label}} checked={option.id === searchParams.get('category')} /** * onKeyDown and onKeyUp handlers disable @@ -211,15 +191,45 @@ export const OnboardingFlowForm: FunctionComponent = () => { ); } }} - /> + > + + {option.description} + + {(option.logos || option.showIntegrationsBadge) && ( + <> + + + {option.logos?.map((logo) => ( + + + + ))} + {option.showIntegrationsBadge && ( + + + + )} + + + )} + ))} {/* Hiding element instead of not rending these elements in order to preload available packages on page load */} -