From 88ac3bf541a2437dd207f32374b0c66e52a3d29c Mon Sep 17 00:00:00 2001 From: ymao1 Date: Wed, 17 Feb 2021 13:43:25 -0500 Subject: [PATCH 01/93] [Actions][Docs] Actions and Connectors API Docs (#90974) * Stubbing out asciidocs * wip * Finishing connector API docs * Cleanup * Removing experimental label * PR fixes * PR fixes * PR fixes --- docs/api/actions-and-connectors.asciidoc | 30 +++++++ .../actions-and-connectors/create.asciidoc | 68 +++++++++++++++ .../actions-and-connectors/delete.asciidoc | 35 ++++++++ .../actions-and-connectors/execute.asciidoc | 83 +++++++++++++++++++ docs/api/actions-and-connectors/get.asciidoc | 50 +++++++++++ .../actions-and-connectors/get_all.asciidoc | 52 ++++++++++++ docs/api/actions-and-connectors/list.asciidoc | 59 +++++++++++++ .../actions-and-connectors/update.asciidoc | 68 +++++++++++++++ .../user/alerting/action-types/email.asciidoc | 1 + .../user/alerting/action-types/index.asciidoc | 1 + docs/user/alerting/action-types/jira.asciidoc | 1 + .../alerting/action-types/pagerduty.asciidoc | 1 + .../alerting/action-types/resilient.asciidoc | 1 + .../alerting/action-types/servicenow.asciidoc | 1 + .../user/alerting/action-types/slack.asciidoc | 1 + .../user/alerting/action-types/teams.asciidoc | 1 + .../alerting/action-types/webhook.asciidoc | 1 + docs/user/api.asciidoc | 1 + 18 files changed, 455 insertions(+) create mode 100644 docs/api/actions-and-connectors.asciidoc create mode 100644 docs/api/actions-and-connectors/create.asciidoc create mode 100644 docs/api/actions-and-connectors/delete.asciidoc create mode 100644 docs/api/actions-and-connectors/execute.asciidoc create mode 100644 docs/api/actions-and-connectors/get.asciidoc create mode 100644 docs/api/actions-and-connectors/get_all.asciidoc create mode 100644 docs/api/actions-and-connectors/list.asciidoc create mode 100644 docs/api/actions-and-connectors/update.asciidoc diff --git a/docs/api/actions-and-connectors.asciidoc b/docs/api/actions-and-connectors.asciidoc new file mode 100644 index 00000000000000..17e7ea1b7672a3 --- /dev/null +++ b/docs/api/actions-and-connectors.asciidoc @@ -0,0 +1,30 @@ +[[actions-and-connectors-api]] +== Action and connector APIs + +Manage Actions and Connectors. + +The following action APIs are available: + +* <> to retrieve a single action by ID + +* <> to retrieve all actions + +* <> to retrieve a list of all action types + +* <> to create actions + +* <> to update the attributes for an existing action + +* <> to execute an action by ID + +* <> to delete an action by ID + +For information about the actions and connectors that {kib} supports, refer to <>. + +include::actions-and-connectors/get.asciidoc[] +include::actions-and-connectors/get_all.asciidoc[] +include::actions-and-connectors/list.asciidoc[] +include::actions-and-connectors/create.asciidoc[] +include::actions-and-connectors/update.asciidoc[] +include::actions-and-connectors/execute.asciidoc[] +include::actions-and-connectors/delete.asciidoc[] diff --git a/docs/api/actions-and-connectors/create.asciidoc b/docs/api/actions-and-connectors/create.asciidoc new file mode 100644 index 00000000000000..af5ddd050e40ec --- /dev/null +++ b/docs/api/actions-and-connectors/create.asciidoc @@ -0,0 +1,68 @@ +[[actions-and-connectors-api-create]] +=== Create action API +++++ +Create action API +++++ + +Creates an action. + +[[actions-and-connectors-api-create-request]] +==== Request + +`POST :/api/actions/action` + +[[actions-and-connectors-api-create-request-body]] +==== Request body + +`name`:: + (Required, string) The display name for the action. + +`actionTypeId`:: + (Required, string) The action type ID for the action. + +`config`:: + (Required, object) The configuration for the action. Configuration properties vary depending on + the action type. For information about the configuration properties, refer to <>. + +`secrets`:: + (Required, object) The secrets configuration for the action. Secrets configuration properties vary + depending on the action type. For information about the secrets configuration properties, refer to <>. + +[[actions-and-connectors-api-create-request-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-api-create-example]] +==== Example + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/actions/action -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' +{ + "name": "my-action", + "actionTypeId": ".index", + "config": { + "index": "test-index" + } +}' +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", + "actionTypeId": ".index", + "name": "my-action", + "config": { + "index": "test-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false +} +-------------------------------------------------- \ No newline at end of file diff --git a/docs/api/actions-and-connectors/delete.asciidoc b/docs/api/actions-and-connectors/delete.asciidoc new file mode 100644 index 00000000000000..e90b9ae44c5bd3 --- /dev/null +++ b/docs/api/actions-and-connectors/delete.asciidoc @@ -0,0 +1,35 @@ +[[actions-and-connectors-api-delete]] +=== Delete action API +++++ +Delete action API +++++ + +Deletes an action by ID. + +WARNING: When you delete an action, _it cannot be recovered_. + +[[actions-and-connectors-api-delete-request]] +==== Request + +`DELETE :/api/actions/action/` + +[[actions-and-connectors-api-delete-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the action. + +[[actions-and-connectors-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/execute.asciidoc b/docs/api/actions-and-connectors/execute.asciidoc new file mode 100644 index 00000000000000..12f1405eb44560 --- /dev/null +++ b/docs/api/actions-and-connectors/execute.asciidoc @@ -0,0 +1,83 @@ +[[actions-and-connectors-api-execute]] +=== Execute action API +++++ +Execute action API +++++ + +Executes an action by ID. + +[[actions-and-connectors-api-execute-request]] +==== Request + +`POST :/api/actions/action//_execute` + +[[actions-and-connectors-api-execute-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the action. + +[[actions-and-connectors-api-execute-request-body]] +==== Request body + +`params`:: + (Required, object) The parameters of the action. Parameter properties vary depending on + the action type. For information about the parameter properties, refer to <>. + +[[actions-and-connectors-api-execute-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-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" +} +-------------------------------------------------- \ No newline at end of file diff --git a/docs/api/actions-and-connectors/get.asciidoc b/docs/api/actions-and-connectors/get.asciidoc new file mode 100644 index 00000000000000..6be554e65db049 --- /dev/null +++ b/docs/api/actions-and-connectors/get.asciidoc @@ -0,0 +1,50 @@ +[[actions-and-connectors-api-get]] +=== Get action API +++++ +Get action API +++++ + +Retrieves an action by ID. + +[[actions-and-connectors-api-get-request]] +==== Request + +`GET :/api/actions/action/` + +[[actions-and-connectors-api-get-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the action. + +[[actions-and-connectors-api-get-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-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-action", + "config": { + "index": "test-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false +} +-------------------------------------------------- diff --git a/docs/api/actions-and-connectors/get_all.asciidoc b/docs/api/actions-and-connectors/get_all.asciidoc new file mode 100644 index 00000000000000..9863963c8395e6 --- /dev/null +++ b/docs/api/actions-and-connectors/get_all.asciidoc @@ -0,0 +1,52 @@ +[[actions-and-connectors-api-get-all]] +=== Get all actions API +++++ +Get all actions API +++++ + +Retrieves all actions. + +[[actions-and-connectors-api-get-all-request]] +==== Request + +`GET :/api/actions` + +[[actions-and-connectors-api-get-all-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-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 + }, + { + "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", + "actionTypeId": ".index", + "name": "my-action", + "config": { + "index": "test-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false + } +] +-------------------------------------------------- diff --git a/docs/api/actions-and-connectors/list.asciidoc b/docs/api/actions-and-connectors/list.asciidoc new file mode 100644 index 00000000000000..b800b7ff3b4f2f --- /dev/null +++ b/docs/api/actions-and-connectors/list.asciidoc @@ -0,0 +1,59 @@ +[[actions-and-connectors-api-list]] +=== List action types API +++++ +List all action types API +++++ + +Retrieves a list of all action types. + +[[actions-and-connectors-api-list-request]] +==== Request + +`GET :/api/actions/list_action_types` + +[[actions-and-connectors-api-list-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-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 action type. +<2> `name` - The name of the action type. +<3> `minimumLicenseRequired` - The license required to use the action type. +<4> `enabled` - Specifies if the action type is enabled or disabled in {kib}. +<5> `enabledInConfig` - Specifies if the action type is enabled or enabled in the {kib} .yml file. +<6> `enabledInLicense` - Specifies if the action type is enabled or disabled in the license. diff --git a/docs/api/actions-and-connectors/update.asciidoc b/docs/api/actions-and-connectors/update.asciidoc new file mode 100644 index 00000000000000..e08ec2f8da1b67 --- /dev/null +++ b/docs/api/actions-and-connectors/update.asciidoc @@ -0,0 +1,68 @@ +[[actions-and-connectors-api-update]] +=== Update action API +++++ +Update action API +++++ + +Updates the attributes for an existing action. + +[[actions-and-connectors-api-update-request]] +==== Request + +`PUT :/api/actions/action/` + +[[actions-and-connectors-api-update-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the action. + +[[actions-and-connectors-api-update-request-body]] +==== Request body + +`name`:: + (Required, string) The new name of the action. + +`config`:: + (Required, object) The new action configuration. Configuration properties vary depending on the action type. For information about the configuration properties, refer to <>. + +`secrets`:: + (Required, object) The updated secrets configuration for the action. Secrets properties vary depending on the action type. For information about the secrets configuration properties, refer to <>. + +[[actions-and-connectors-api-update-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[actions-and-connectors-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-action", + "config": { + "index": "updated-index" + } +}' +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad", + "actionTypeId": ".index", + "name": "updated-action", + "config": { + "index": "updated-index", + "refresh": false, + "executionTimeField": null + }, + "isPreconfigured": false +} +-------------------------------------------------- diff --git a/docs/user/alerting/action-types/email.asciidoc b/docs/user/alerting/action-types/email.asciidoc index d7a9373a6e2a99..3562be1e405f6f 100644 --- a/docs/user/alerting/action-types/email.asciidoc +++ b/docs/user/alerting/action-types/email.asciidoc @@ -37,6 +37,7 @@ Password:: password for 'login' type authentication. password: passwordkeystorevalue -- +[[email-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/alerting/action-types/index.asciidoc b/docs/user/alerting/action-types/index.asciidoc index 2c6da7c7c3026b..2f459edea28f14 100644 --- a/docs/user/alerting/action-types/index.asciidoc +++ b/docs/user/alerting/action-types/index.asciidoc @@ -30,6 +30,7 @@ Execution time field:: This field will be automatically set to the time the ale executionTimeField: somedate -- +[[index-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/alerting/action-types/jira.asciidoc b/docs/user/alerting/action-types/jira.asciidoc index 65e5ee4fc4a013..6e47d5618d5982 100644 --- a/docs/user/alerting/action-types/jira.asciidoc +++ b/docs/user/alerting/action-types/jira.asciidoc @@ -33,6 +33,7 @@ API token (or password):: Jira API authentication token (or password) for HTTP apiToken: tokenkeystorevalue -- +[[jira-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/alerting/action-types/pagerduty.asciidoc b/docs/user/alerting/action-types/pagerduty.asciidoc index aad192dbddb30f..e1078a55ddd0d5 100644 --- a/docs/user/alerting/action-types/pagerduty.asciidoc +++ b/docs/user/alerting/action-types/pagerduty.asciidoc @@ -150,6 +150,7 @@ Integration Key:: A 32 character PagerDuty Integration Key for an integration routingKey: testroutingkey -- +[[pagerduty-connector-config-properties]] `config` defines the action type specific to the configuration. `config` contains `apiURL`, a string that corresponds to *API URL*. diff --git a/docs/user/alerting/action-types/resilient.asciidoc b/docs/user/alerting/action-types/resilient.asciidoc index b5ddb76d49b0cd..112246ab91162e 100644 --- a/docs/user/alerting/action-types/resilient.asciidoc +++ b/docs/user/alerting/action-types/resilient.asciidoc @@ -33,6 +33,7 @@ API key secret:: The authentication key secret for HTTP Basic authentication. apiKeySecret: tokenkeystorevalue -- +[[resilient-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/alerting/action-types/servicenow.asciidoc b/docs/user/alerting/action-types/servicenow.asciidoc index 0acb92bcdb5ee5..5d8782c14e5815 100644 --- a/docs/user/alerting/action-types/servicenow.asciidoc +++ b/docs/user/alerting/action-types/servicenow.asciidoc @@ -31,6 +31,7 @@ Password:: Password for HTTP Basic authentication. password: passwordkeystorevalue -- +[[servicenow-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/alerting/action-types/slack.asciidoc b/docs/user/alerting/action-types/slack.asciidoc index a1fe7a2521b22a..6a38e5c827ab28 100644 --- a/docs/user/alerting/action-types/slack.asciidoc +++ b/docs/user/alerting/action-types/slack.asciidoc @@ -26,6 +26,7 @@ Webhook URL:: The URL of the incoming webhook. See https://api.slack.com/messa webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz' -- +[[slack-connector-config-properties]] `config` defines the action type specific to the configuration. `config` contains `webhookUrl`, a string that corresponds to *Webhook URL*. diff --git a/docs/user/alerting/action-types/teams.asciidoc b/docs/user/alerting/action-types/teams.asciidoc index 6706dd2e5643fd..e1ce91fc0c1231 100644 --- a/docs/user/alerting/action-types/teams.asciidoc +++ b/docs/user/alerting/action-types/teams.asciidoc @@ -26,6 +26,7 @@ Webhook URL:: The URL of the incoming webhook. See https://docs.microsoft.com/ webhookUrl: 'https://outlook.office.com/webhook/abcd@0123456/IncomingWebhook/abcdefgh/ijklmnopqrstuvwxyz' -- +[[teams-connector-config-properties]] `config` defines the action type specific to the configuration. `config` contains `webhookUrl`, a string that corresponds to *Webhook URL*. diff --git a/docs/user/alerting/action-types/webhook.asciidoc b/docs/user/alerting/action-types/webhook.asciidoc index fff6814325ea45..2d626d53d1c77e 100644 --- a/docs/user/alerting/action-types/webhook.asciidoc +++ b/docs/user/alerting/action-types/webhook.asciidoc @@ -36,6 +36,7 @@ Password:: An optional password. If set, HTTP basic authentication is used. Cur password: passwordkeystorevalue -- +[[webhook-connector-config-properties]] `config` defines the action type specific to the configuration and contains the following properties: [cols="2*<"] diff --git a/docs/user/api.asciidoc b/docs/user/api.asciidoc index 20f1fc89367f29..2ae83bee1e06c7 100644 --- a/docs/user/api.asciidoc +++ b/docs/user/api.asciidoc @@ -36,6 +36,7 @@ include::{kib-repo-dir}/api/features.asciidoc[] include::{kib-repo-dir}/api/spaces-management.asciidoc[] include::{kib-repo-dir}/api/role-management.asciidoc[] include::{kib-repo-dir}/api/saved-objects.asciidoc[] +include::{kib-repo-dir}/api/actions-and-connectors.asciidoc[] include::{kib-repo-dir}/api/dashboard-api.asciidoc[] include::{kib-repo-dir}/api/logstash-configuration-management.asciidoc[] include::{kib-repo-dir}/api/url-shortening.asciidoc[] From 119199ef41c11df2d0fc4cd4dc55d2db6c500f3a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 17 Feb 2021 19:45:06 +0100 Subject: [PATCH 02/93] [coverage] change worker, save json files in original path (#91683) * [coverage] write data to original path * [coverage] change worker size --- .ci/Jenkinsfile_coverage | 2 +- test/functional/services/remote/remote.ts | 15 +++++++++++---- test/scripts/jenkins_ci_group.sh | 8 -------- test/scripts/jenkins_unit.sh | 2 +- test/scripts/jenkins_xpack_ci_group.sh | 8 -------- 5 files changed, 13 insertions(+), 22 deletions(-) diff --git a/.ci/Jenkinsfile_coverage b/.ci/Jenkinsfile_coverage index b804e3071448e7..0aa962a58f53cc 100644 --- a/.ci/Jenkinsfile_coverage +++ b/.ci/Jenkinsfile_coverage @@ -10,7 +10,7 @@ kibanaPipeline(timeoutMinutes: 300) { "TIME_STAMP=${timestamp}", 'CODE_COVERAGE=1', // Enables coverage. Needed for multiple ci scripts, such as remote.ts, test/scripts/*.sh, schema.js, etc. ]) { - workers.base(name: 'coverage-worker', size: 'l', ramDisk: false, bootstrapped: false) { + workers.base(name: 'coverage-worker', size: 'xl', ramDisk: false, bootstrapped: false) { catchError { kibanaPipeline.bash(""" diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index f731ffade6efc7..5bf99b4bf1136b 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -37,14 +37,21 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { }; const writeCoverage = (coverageJson: string) => { - if (!Fs.existsSync(coverageDir)) { - Fs.mkdirSync(coverageDir, { recursive: true }); + // on CI we make hard link clone and run tests from kibana${process.env.CI_GROUP} root path + const re = new RegExp(`kibana${process.env.CI_GROUP}`, 'g'); + const dir = process.env.CI ? coverageDir.replace(re, 'kibana') : coverageDir; + + if (!Fs.existsSync(dir)) { + Fs.mkdirSync(dir, { recursive: true }); } + const id = coverageCounter++; const timestamp = Date.now(); - const path = resolve(coverageDir, `${id}.${timestamp}.coverage.json`); + const path = resolve(dir, `${id}.${timestamp}.coverage.json`); log.info('writing coverage to', path); - Fs.writeFileSync(path, JSON.stringify(JSON.parse(coverageJson), null, 2)); + + const jsonString = process.env.CI ? coverageJson.replace(re, 'kibana') : coverageJson; + Fs.writeFileSync(path, JSON.stringify(JSON.parse(jsonString), null, 2)); }; const browserConfig: BrowserConfig = { diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh index 4faf645975c778..58dcc9f52c0893 100755 --- a/test/scripts/jenkins_ci_group.sh +++ b/test/scripts/jenkins_ci_group.sh @@ -29,14 +29,6 @@ else echo " -> running tests from the clone folder" node scripts/functional_tests --debug --include-tag "ciGroup$CI_GROUP" --exclude-tag "skipCoverage" || true; - if [[ -d target/kibana-coverage/functional ]]; then - echo " -> replacing kibana${CI_GROUP} with kibana in json files" - sed -i "s|kibana${CI_GROUP}|kibana|g" target/kibana-coverage/functional/*.json - echo " -> copying coverage to the original folder" - mkdir -p ../kibana/target/kibana-coverage/functional - mv target/kibana-coverage/functional/* ../kibana/target/kibana-coverage/functional/ - fi - echo " -> moving junit output, silently fail in case of no report" mkdir -p ../kibana/target/junit mv target/junit/* ../kibana/target/junit/ || echo "copying junit failed" diff --git a/test/scripts/jenkins_unit.sh b/test/scripts/jenkins_unit.sh index a483f8378b8b41..3300ed2d0884f9 100755 --- a/test/scripts/jenkins_unit.sh +++ b/test/scripts/jenkins_unit.sh @@ -28,7 +28,7 @@ if [[ -z "$CODE_COVERAGE" ]] ; then ./test/scripts/checks/test_hardening.sh else echo " -> Running jest tests with coverage" - node scripts/jest --ci --verbose --maxWorkers=6 --coverage || true; + node scripts/jest --ci --verbose --maxWorkers=8 --coverage || true; echo " -> Running jest integration tests with coverage" node scripts/jest_integration --ci --verbose --coverage || true; diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index 648605135b3595..0198a5d0ac5fac 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -25,14 +25,6 @@ else echo " -> running tests from the clone folder" node scripts/functional_tests --debug --include-tag "ciGroup$CI_GROUP" --exclude-tag "skipCoverage" || true; - if [[ -d ../target/kibana-coverage/functional ]]; then - echo " -> replacing kibana${CI_GROUP} with kibana in json files" - sed -i "s|kibana${CI_GROUP}|kibana|g" ../target/kibana-coverage/functional/*.json - echo " -> copying coverage to the original folder" - mkdir -p ../../kibana/target/kibana-coverage/functional - mv ../target/kibana-coverage/functional/* ../../kibana/target/kibana-coverage/functional/ - fi - echo " -> moving junit output, silently fail in case of no report" mkdir -p ../../kibana/target/junit mv ../target/junit/* ../../kibana/target/junit/ || echo "copying junit failed" From 03c423f11d9d02c84e8499ed07323013d537c494 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Wed, 17 Feb 2021 20:11:54 +0100 Subject: [PATCH 03/93] [APM] use top_metrics aggregation where appropriate (#91479) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Søren Louv-Jansen --- .../lib/environments/get_all_environments.ts | 4 ++-- .../__snapshots__/queries.test.ts.snap | 18 +++++++------- .../get_destination_map.ts | 21 ++++++++-------- .../get_service_transaction_stats.ts | 16 +++++++------ .../__snapshots__/queries.test.ts.snap | 24 +++++++++++-------- .../get_transaction_group_stats.ts | 18 +++++++------- .../distribution/get_buckets/index.ts | 13 ++++++---- x-pack/plugins/apm/server/routes/services.ts | 1 + .../traces/__snapshots__/top_traces.snap | 2 +- .../tests/transactions/distribution.ts | 8 +++---- 10 files changed, 69 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts index 8fedcf6224e3c9..1bf01c24776fb0 100644 --- a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts +++ b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts @@ -31,8 +31,8 @@ export async function getAllEnvironments({ includeMissing?: boolean; }) { const spanName = serviceName - ? 'get_all_environments_for_all_services' - : 'get_all_environments_for_service'; + ? 'get_all_environments_for_service' + : 'get_all_environments_for_all_services'; return withApmSpan(spanName, async () => { const { apmEventClient, config } = setup; const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index 606ce870351564..3e68831ee7cba7 100644 --- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -101,14 +101,6 @@ Array [ "aggs": Object { "transactionType": Object { "aggs": Object { - "agentName": Object { - "top_hits": Object { - "docvalue_fields": Array [ - "agent.name", - ], - "size": 1, - }, - }, "avg_duration": Object { "avg": Object { "field": "transaction.duration.us", @@ -129,6 +121,16 @@ Array [ ], }, }, + "sample": Object { + "top_metrics": Object { + "metrics": Object { + "field": "agent.name", + }, + "sort": Object { + "@timestamp": "desc", + }, + }, + }, "timeseries": Object { "aggs": Object { "avg_duration": Object { diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts index ef2b50cbdbedfe..aa53e8da6cad05 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts @@ -71,14 +71,13 @@ export const getDestinationMap = ({ ], }, aggs: { - docs: { - top_hits: { - docvalue_fields: [ - SPAN_TYPE, - SPAN_SUBTYPE, - SPAN_ID, + sample: { + top_metrics: { + metrics: [ + { field: SPAN_TYPE }, + { field: SPAN_SUBTYPE }, + { field: SPAN_ID }, ] as const, - _source: false, sort: { '@timestamp': 'desc', }, @@ -93,15 +92,15 @@ export const getDestinationMap = ({ const outgoingConnections = response.aggregations?.connections.buckets.map((bucket) => { - const doc = bucket.docs.hits.hits[0]; + const fieldValues = bucket.sample.top[0].metrics; return { [SPAN_DESTINATION_SERVICE_RESOURCE]: String( bucket.key[SPAN_DESTINATION_SERVICE_RESOURCE] ), - [SPAN_ID]: String(doc.fields[SPAN_ID]?.[0]), - [SPAN_TYPE]: String(doc.fields[SPAN_TYPE]?.[0] ?? ''), - [SPAN_SUBTYPE]: String(doc.fields[SPAN_SUBTYPE]?.[0] ?? ''), + [SPAN_ID]: (fieldValues[SPAN_ID] ?? '') as string, + [SPAN_TYPE]: (fieldValues[SPAN_TYPE] ?? '') as string, + [SPAN_SUBTYPE]: (fieldValues[SPAN_SUBTYPE] ?? '') as string, }; }) ?? []; diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts index 36540b01a07cc9..acfdc1d8c1710a 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts @@ -98,10 +98,12 @@ export async function getServiceTransactionStats({ missing: '', }, }, - agentName: { - top_hits: { - docvalue_fields: [AGENT_NAME] as const, - size: 1, + sample: { + top_metrics: { + metrics: { field: AGENT_NAME } as const, + sort: { + '@timestamp': 'desc', + }, }, }, timeseries: { @@ -139,9 +141,9 @@ export async function getServiceTransactionStats({ environments: topTransactionTypeBucket.environments.buckets .map((environmentBucket) => environmentBucket.key as string) .filter(Boolean), - agentName: topTransactionTypeBucket.agentName.hits.hits[0].fields[ - 'agent.name' - ]?.[0] as AgentName, + agentName: topTransactionTypeBucket.sample.top[0].metrics[ + AGENT_NAME + ] as AgentName, avgResponseTime: { value: topTransactionTypeBucket.avg_duration.value, timeseries: topTransactionTypeBucket.timeseries.buckets.map( diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap index 89069d74bacf8b..443159611883fc 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap @@ -13,11 +13,13 @@ Array [ "transaction_groups": Object { "aggs": Object { "transaction_type": Object { - "top_hits": Object { - "_source": Array [ - "transaction.type", - ], - "size": 1, + "top_metrics": Object { + "metrics": Object { + "field": "transaction.type", + }, + "sort": Object { + "@timestamp": "desc", + }, }, }, }, @@ -222,11 +224,13 @@ Array [ "transaction_groups": Object { "aggs": Object { "transaction_type": Object { - "top_hits": Object { - "_source": Array [ - "transaction.type", - ], - "size": 1, + "top_metrics": Object { + "metrics": Object { + "field": "transaction.type", + }, + "sort": Object { + "@timestamp": "desc", + }, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts index ee19a6a8d15918..5ee46bf1a5918a 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts @@ -69,9 +69,13 @@ export function getCounts({ request, setup }: MetricParams) { return withApmSpan('get_transaction_group_transaction_count', async () => { const params = mergeRequestWithAggs(request, { transaction_type: { - top_hits: { - size: 1, - _source: [TRANSACTION_TYPE], + top_metrics: { + sort: { + '@timestamp': 'desc', + }, + metrics: { + field: TRANSACTION_TYPE, + } as const, }, }, }); @@ -81,14 +85,12 @@ export function getCounts({ request, setup }: MetricParams) { return arrayUnionToCallable( response.aggregations?.transaction_groups.buckets ?? [] ).map((bucket) => { - // type is Transaction | APMBaseDoc because it could be a metric document - const source = (bucket.transaction_type.hits.hits[0] - ._source as unknown) as { transaction: { type: string } }; - return { key: bucket.key as BucketKey, count: bucket.doc_count, - transactionType: source.transaction.type, + transactionType: bucket.transaction_type.top[0].metrics[ + TRANSACTION_TYPE + ] as string, }; }); }); diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts index 22a34ded4c20d5..d1d23f538e96b0 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts @@ -109,8 +109,11 @@ export async function getBuckets({ }), aggs: { samples: { - top_hits: { - _source: [TRANSACTION_ID, TRACE_ID], + top_metrics: { + metrics: [ + { field: TRANSACTION_ID }, + { field: TRACE_ID }, + ] as const, size: 10, sort: { _score: 'desc', @@ -128,9 +131,9 @@ export async function getBuckets({ response.aggregations?.distribution.buckets.map((bucket) => { return { key: bucket.key, - samples: bucket.samples.hits.hits.map((hit) => ({ - traceId: hit._source.trace.id, - transactionId: hit._source.transaction.id, + samples: bucket.samples.top.map((sample) => ({ + traceId: sample.metrics[TRACE_ID] as string, + transactionId: sample.metrics[TRANSACTION_ID] as string, })), }; }) ?? [] diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index ff064e0571d138..84ccb4b06c2e6a 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -304,6 +304,7 @@ export const serviceErrorGroupsRoute = createRoute({ transactionType, }, } = context.params; + return getServiceErrorGroups({ serviceName, setup, diff --git a/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap index c8104b9858027c..ad9eca1cc0900c 100644 --- a/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap +++ b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap @@ -995,7 +995,7 @@ Array [ }, "serviceName": "kibana-frontend", "transactionName": "/app/dev_tools", - "transactionType": "page-load", + "transactionType": "route-change", "transactionsPerMinute": 0.0666666666666667, }, Object { diff --git a/x-pack/test/apm_api_integration/tests/transactions/distribution.ts b/x-pack/test/apm_api_integration/tests/transactions/distribution.ts index e46ec7a181fc00..56d5e217068a4b 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/distribution.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/distribution.ts @@ -83,16 +83,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { .toMatchInline(` Array [ Object { - "traceId": "af0f18dc0841cfc1f567e7e1d55cfda7", - "transactionId": "925f02e5ac122897", + "traceId": "a4eb3781a21dc11d289293076fd1a1b3", + "transactionId": "21892bde4ff1364d", }, Object { "traceId": "ccd327537120e857bdfa407434dfb9a4", "transactionId": "c5f923159cc1b8a6", }, Object { - "traceId": "a4eb3781a21dc11d289293076fd1a1b3", - "transactionId": "21892bde4ff1364d", + "traceId": "af0f18dc0841cfc1f567e7e1d55cfda7", + "transactionId": "925f02e5ac122897", }, ] `); From a3dfecb03f2f2a17c29c0f89064b6dad201afda7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Feb 2021 13:23:51 -0600 Subject: [PATCH 04/93] Update dependency @elastic/charts to v25 (#91557) --- package.json | 2 +- .../public/static/utils/transform_click_event.ts | 7 ++----- .../public/components/detailed_tooltip.tsx | 7 ++----- .../public/utils/get_legend_actions.tsx | 2 +- .../public/utils/use_color_picker.tsx | 2 +- src/plugins/vis_type_xy/public/vis_component.tsx | 16 +++------------- .../components/common/charts/duration_chart.tsx | 4 ++-- yarn.lock | 8 ++++---- 8 files changed, 16 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index cc8ab44c51c96a..a7959f047ab5c0 100644 --- a/package.json +++ b/package.json @@ -351,7 +351,7 @@ "@cypress/webpack-preprocessor": "^5.5.0", "@elastic/apm-rum": "^5.6.1", "@elastic/apm-rum-react": "^1.2.5", - "@elastic/charts": "24.6.0", + "@elastic/charts": "25.0.1", "@elastic/eslint-config-kibana": "link:packages/elastic-eslint-config-kibana", "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", diff --git a/src/plugins/charts/public/static/utils/transform_click_event.ts b/src/plugins/charts/public/static/utils/transform_click_event.ts index e875967616bbdb..0c303b92bf1a1c 100644 --- a/src/plugins/charts/public/static/utils/transform_click_event.ts +++ b/src/plugins/charts/public/static/utils/transform_click_event.ts @@ -30,9 +30,6 @@ export interface BrushTriggerEvent { type AllSeriesAccessors = Array<[accessor: Accessor | AccessorFn, value: string | number]>; -// TODO: replace when exported from elastic/charts -const DEFAULT_SINGLE_PANEL_SM_VALUE = '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__'; - /** * returns accessor value from string or function accessor * @param datum @@ -97,11 +94,11 @@ function getSplitChartValue({ | string | number | undefined { - if (smHorizontalAccessorValue !== DEFAULT_SINGLE_PANEL_SM_VALUE) { + if (smHorizontalAccessorValue !== undefined) { return smHorizontalAccessorValue; } - if (smVerticalAccessorValue !== DEFAULT_SINGLE_PANEL_SM_VALUE) { + if (smVerticalAccessorValue !== undefined) { return smVerticalAccessorValue; } diff --git a/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx b/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx index 0c1ab262755a73..c9ed82fcf58e55 100644 --- a/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx +++ b/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx @@ -27,9 +27,6 @@ interface TooltipData { value: string; } -// TODO: replace when exported from elastic/charts -const DEFAULT_SINGLE_PANEL_SM_VALUE = '__ECH_DEFAULT_SINGLE_PANEL_SM_VALUE__'; - export const getTooltipData = ( aspects: Aspects, header: TooltipValue | null, @@ -81,7 +78,7 @@ export const getTooltipData = ( if ( aspects.splitColumn && valueSeries.smHorizontalAccessorValue !== undefined && - valueSeries.smHorizontalAccessorValue !== DEFAULT_SINGLE_PANEL_SM_VALUE + valueSeries.smHorizontalAccessorValue !== undefined ) { data.push({ label: aspects.splitColumn.title, @@ -92,7 +89,7 @@ export const getTooltipData = ( if ( aspects.splitRow && valueSeries.smVerticalAccessorValue !== undefined && - valueSeries.smVerticalAccessorValue !== DEFAULT_SINGLE_PANEL_SM_VALUE + valueSeries.smVerticalAccessorValue !== undefined ) { data.push({ label: aspects.splitRow.title, diff --git a/src/plugins/vis_type_xy/public/utils/get_legend_actions.tsx b/src/plugins/vis_type_xy/public/utils/get_legend_actions.tsx index 9f557806cf1427..5c28ca77da0c4a 100644 --- a/src/plugins/vis_type_xy/public/utils/get_legend_actions.tsx +++ b/src/plugins/vis_type_xy/public/utils/get_legend_actions.tsx @@ -20,7 +20,7 @@ export const getLegendActions = ( onFilter: (data: ClickTriggerEvent, negate?: any) => void, getSeriesName: (series: XYChartSeriesIdentifier) => SeriesName ): LegendAction => { - return ({ series: xySeries }) => { + return ({ series: [xySeries] }) => { const [popoverOpen, setPopoverOpen] = useState(false); const [isfilterable, setIsfilterable] = useState(false); const series = xySeries as XYChartSeriesIdentifier; diff --git a/src/plugins/vis_type_xy/public/utils/use_color_picker.tsx b/src/plugins/vis_type_xy/public/utils/use_color_picker.tsx index 80e7e12adf799d..5028bc379c375d 100644 --- a/src/plugins/vis_type_xy/public/utils/use_color_picker.tsx +++ b/src/plugins/vis_type_xy/public/utils/use_color_picker.tsx @@ -36,7 +36,7 @@ export const useColorPicker = ( getSeriesName: (series: XYChartSeriesIdentifier) => SeriesName ): LegendColorPicker => useMemo( - () => ({ anchor, color, onClose, onChange, seriesIdentifier }) => { + () => ({ anchor, color, onClose, onChange, seriesIdentifiers: [seriesIdentifier] }) => { const seriesName = getSeriesName(seriesIdentifier as XYChartSeriesIdentifier); const handlChange = (newColor: string | null, event: BaseSyntheticEvent) => { if (!seriesName) { diff --git a/src/plugins/vis_type_xy/public/vis_component.tsx b/src/plugins/vis_type_xy/public/vis_component.tsx index c7ba5021196ff7..ab398101bac9d6 100644 --- a/src/plugins/vis_type_xy/public/vis_component.tsx +++ b/src/plugins/vis_type_xy/public/vis_component.tsx @@ -157,17 +157,12 @@ const VisComponent = (props: VisComponentProps) => { ( visData: Datatable, xAccessor: Accessor | AccessorFn, - splitSeriesAccessors: Array, - splitChartAccessor?: Accessor | AccessorFn + splitSeriesAccessors: Array ) => { const splitSeriesAccessorFnMap = getSplitSeriesAccessorFnMap(splitSeriesAccessors); return (series: XYChartSeriesIdentifier): ClickTriggerEvent | null => { if (xAccessor !== null) { - return getFilterFromSeriesFn(visData)( - series, - splitSeriesAccessorFnMap, - splitChartAccessor - ); + return getFilterFromSeriesFn(visData)(series, splitSeriesAccessorFnMap); } return null; @@ -373,12 +368,7 @@ const VisComponent = (props: VisComponentProps) => { config.aspects.series && (config.aspects.series?.length ?? 0) > 0 ? getLegendActions( canFilter, - getFilterEventData( - visData, - xAccessor, - splitSeriesAccessors, - splitChartColumnAccessor ?? splitChartRowAccessor - ), + getFilterEventData(visData, xAccessor, splitSeriesAccessors), handleFilterAction, getSeriesName ) diff --git a/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx b/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx index b91b50666cf07b..68b9ab0435aaac 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx @@ -15,8 +15,8 @@ import { Position, timeFormatter, Settings, - SeriesIdentifier, BrushEndListener, + LegendItemListener, } from '@elastic/charts'; import { getChartDateLabel } from '../../../lib/helper'; import { LocationDurationLine } from '../../../../common/types'; @@ -75,7 +75,7 @@ export const DurationChartComponent = ({ }); }; - const legendToggleVisibility = (legendItem: SeriesIdentifier | null) => { + const legendToggleVisibility: LegendItemListener = ([legendItem]) => { if (legendItem) { setHiddenLegends((prevState) => { if (prevState.includes(legendItem.specId)) { diff --git a/yarn.lock b/yarn.lock index 1a939e3dddf579..6cb2a6864eb752 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2138,10 +2138,10 @@ dependencies: object-hash "^1.3.0" -"@elastic/charts@24.6.0": - version "24.6.0" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-24.6.0.tgz#2123c72e69e1e4557be41ae55c085a5a9f75d3b6" - integrity sha512-fL0301EcHxJEYRzdlD4JIA3VXY4qwRPSkRrk8hvJNryTlQWEdyXZF3HNczk0IrgST5cfCOGAWG8IVtO59HxUJw== +"@elastic/charts@25.0.1": + version "25.0.1" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-25.0.1.tgz#7c61fc22887b7b1feba3e52fc1b44f21a9c1d0bc" + integrity sha512-UYGO9Yg2+cdJOOs9DjlYeB2Oy/3UMXTtF6/yWZt2iXh7mmW9jI5DqfjgG/9BFSCwQZHUKBZ58d5ZCQfe72maGA== dependencies: "@popperjs/core" "^2.4.0" chroma-js "^2.1.0" From f650c38e12ae09caa1e5bd76dc39f66639a0b9ba Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Wed, 17 Feb 2021 14:28:50 -0600 Subject: [PATCH 05/93] Remove environment from uiFilters (#89647) --- .../plugins/apm/common/utils/queries.test.ts | 33 +++++ .../utils/queries.ts} | 30 +++- .../plugins/apm/common/utils/range_filter.ts | 16 -- .../app/ErrorGroupDetails/index.tsx | 5 +- .../app/correlations/error_correlations.tsx | 10 +- .../app/correlations/latency_correlations.tsx | 10 +- .../app/error_group_overview/index.tsx | 5 +- .../app/service_inventory/index.tsx | 11 +- .../index.tsx | 7 +- .../service_overview_errors_table/index.tsx | 4 +- ...ice_overview_instances_chart_and_table.tsx | 5 +- .../service_overview_throughput_chart.tsx | 5 +- .../index.tsx | 5 +- .../components/app/trace_overview/index.tsx | 5 +- .../use_transaction_list.ts | 5 +- .../shared/EnvironmentFilter/index.tsx | 5 +- .../use_transaction_breakdown.ts | 13 +- .../transaction_error_rate_chart/index.tsx | 13 +- .../url_params_context/resolve_url_params.ts | 3 +- .../url_params_context/url_params_context.tsx | 2 - .../use_error_group_distribution_fetcher.tsx | 5 +- .../use_service_metric_charts_fetcher.ts | 13 +- .../use_transaction_distribution_fetcher.ts | 12 +- .../use_transaction_latency_chart_fetcher.ts | 10 +- ...se_transaction_throughput_chart_fetcher.ts | 13 +- .../plugins/apm/public/utils/testHelpers.tsx | 4 +- .../chart_preview/get_transaction_duration.ts | 7 +- .../get_transaction_error_count.ts | 7 +- .../get_transaction_error_rate.ts | 7 +- .../alerts/register_error_count_alert_type.ts | 4 +- ...egister_transaction_duration_alert_type.ts | 4 +- ...ister_transaction_error_rate_alert_type.ts | 4 +- .../create_anomaly_detection_jobs.ts | 6 +- .../index.ts | 9 +- .../index.ts | 9 +- .../lib/environments/get_environments.ts | 5 +- .../errors/distribution/get_buckets.test.ts | 11 +- .../lib/errors/distribution/get_buckets.ts | 7 +- .../errors/distribution/get_distribution.ts | 3 + .../lib/errors/get_error_group_sample.ts | 7 +- .../apm/server/lib/errors/get_error_groups.ts | 8 +- .../helpers/aggregated_transactions/index.ts | 4 +- .../get_environment_ui_filter_es.test.ts | 32 ---- .../convert_ui_filters/get_es_filter.ts | 6 +- .../__snapshots__/queries.test.ts.snap | 140 +++++++++--------- .../server/lib/metrics/by_agent/default.ts | 17 ++- .../java/gc/fetch_and_transform_gc_metrics.ts | 3 + .../by_agent/java/gc/get_gc_rate_chart.ts | 3 + .../by_agent/java/gc/get_gc_time_chart.ts | 3 + .../by_agent/java/heap_memory/index.ts | 3 + .../server/lib/metrics/by_agent/java/index.ts | 21 ++- .../by_agent/java/non_heap_memory/index.ts | 3 + .../by_agent/java/thread_count/index.ts | 3 + .../lib/metrics/by_agent/shared/cpu/index.ts | 3 + .../metrics/by_agent/shared/memory/index.ts | 4 + .../metrics/fetch_and_transform_metrics.ts | 3 + .../get_metrics_chart_data_by_agent.ts | 11 +- .../get_service_count.ts | 4 +- .../get_transaction_coordinates.ts | 4 +- .../apm/server/lib/rum_client/has_rum_data.ts | 6 +- .../fetch_service_paths_from_trace_ids.ts | 4 +- .../lib/service_map/get_service_anomalies.ts | 16 +- .../server/lib/service_map/get_service_map.ts | 13 +- .../get_service_map_from_trace_ids.ts | 9 +- .../get_service_map_service_node_info.test.ts | 4 +- .../get_service_map_service_node_info.ts | 17 +-- .../lib/service_map/get_trace_sample_ids.ts | 9 +- .../__snapshots__/queries.test.ts.snap | 28 ++-- .../get_derived_service_annotations.ts | 7 +- .../annotations/get_stored_annotations.ts | 13 +- .../lib/services/get_service_agent_name.ts | 4 +- .../get_destination_map.ts | 11 +- .../get_service_dependencies/get_metrics.ts | 9 +- .../get_service_dependencies/index.ts | 2 +- .../get_service_error_groups/index.ts | 10 +- ...et_service_instance_system_metric_stats.ts | 6 +- .../get_service_instance_transaction_stats.ts | 6 +- .../services/get_service_instances/index.ts | 1 + .../services/get_service_metadata_details.ts | 4 +- .../services/get_service_metadata_icons.ts | 4 +- ...transaction_group_comparison_statistics.ts | 7 +- .../get_service_transaction_groups.ts | 7 +- .../services/get_service_transaction_types.ts | 4 +- .../get_services/get_health_statuses.ts | 17 +-- .../get_service_transaction_stats.ts | 9 +- .../get_services/get_services_items.ts | 6 +- .../server/lib/services/get_services/index.ts | 3 + .../apm/server/lib/services/get_throughput.ts | 11 +- .../apm/server/lib/traces/get_trace_items.ts | 6 +- .../__snapshots__/queries.test.ts.snap | 64 ++++---- .../server/lib/transaction_groups/fetcher.ts | 2 + .../lib/transaction_groups/get_error_rate.ts | 11 +- .../lib/transactions/breakdown/index.ts | 7 +- .../distribution/get_buckets/index.ts | 10 +- .../distribution/get_distribution_max.ts | 14 +- .../lib/transactions/distribution/index.ts | 4 + .../transactions/get_anomaly_data/fetcher.ts | 11 +- .../transactions/get_anomaly_data/index.ts | 20 +-- .../transactions/get_latency_charts/index.ts | 14 +- .../get_throughput_charts/index.ts | 16 +- .../lib/transactions/get_transaction/index.ts | 4 +- .../plugins/apm/server/projections/errors.ts | 7 +- .../plugins/apm/server/projections/metrics.ts | 7 +- .../projections/rum_page_load_transactions.ts | 6 +- .../apm/server/projections/services.ts | 4 +- .../apm/server/projections/transactions.ts | 11 +- .../plugins/apm/server/routes/correlations.ts | 9 +- .../apm/server/routes/create_apm_api.ts | 8 +- .../apm/server/routes/default_api_types.ts | 2 + x-pack/plugins/apm/server/routes/errors.ts | 17 ++- x-pack/plugins/apm/server/routes/metrics.ts | 6 +- .../plugins/apm/server/routes/service_map.ts | 4 +- x-pack/plugins/apm/server/routes/services.ts | 25 +++- x-pack/plugins/apm/server/routes/traces.ts | 8 +- .../plugins/apm/server/routes/transactions.ts | 56 ++++--- .../plugins/apm/server/utils/test_helpers.tsx | 2 +- .../tests/csm/has_rum_data.ts | 2 +- .../tests/feature_controls.ts | 8 +- .../tests/services/top_services.ts | 4 +- .../transactions/__snapshots__/latency.snap | 4 +- .../tests/transactions/latency.ts | 47 +++--- .../tests/transactions/throughput.ts | 4 +- 122 files changed, 757 insertions(+), 528 deletions(-) create mode 100644 x-pack/plugins/apm/common/utils/queries.test.ts rename x-pack/plugins/apm/{server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts => common/utils/queries.ts} (52%) delete mode 100644 x-pack/plugins/apm/common/utils/range_filter.ts delete mode 100644 x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.test.ts diff --git a/x-pack/plugins/apm/common/utils/queries.test.ts b/x-pack/plugins/apm/common/utils/queries.test.ts new file mode 100644 index 00000000000000..546c8627def69c --- /dev/null +++ b/x-pack/plugins/apm/common/utils/queries.test.ts @@ -0,0 +1,33 @@ +/* + * 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 { SERVICE_ENVIRONMENT } from '../elasticsearch_fieldnames'; +import { ENVIRONMENT_NOT_DEFINED } from '../environment_filter_values'; +import { environmentQuery } from './queries'; + +describe('environmentQuery', () => { + describe('when environment is undefined', () => { + it('returns an empty query', () => { + expect(environmentQuery()).toEqual([]); + }); + }); + + it('creates a query for a service environment', () => { + expect(environmentQuery('test')).toEqual([ + { + term: { [SERVICE_ENVIRONMENT]: 'test' }, + }, + ]); + }); + + it('creates a query for missing service environments', () => { + expect(environmentQuery(ENVIRONMENT_NOT_DEFINED.value)[0]).toHaveProperty( + ['bool', 'must_not', 'exists', 'field'], + SERVICE_ENVIRONMENT + ); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts b/x-pack/plugins/apm/common/utils/queries.ts similarity index 52% rename from x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts rename to x-pack/plugins/apm/common/utils/queries.ts index 3dea3344085c92..dbbbf324b964a9 100644 --- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.ts +++ b/x-pack/plugins/apm/common/utils/queries.ts @@ -5,19 +5,41 @@ * 2.0. */ -import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../typings/elasticsearch'; import { ENVIRONMENT_NOT_DEFINED, ENVIRONMENT_ALL, -} from '../../../../common/environment_filter_values'; -import { SERVICE_ENVIRONMENT } from '../../../../common/elasticsearch_fieldnames'; +} from '../environment_filter_values'; +import { SERVICE_ENVIRONMENT } from '../elasticsearch_fieldnames'; -export function getEnvironmentUiFilterES(environment?: string): ESFilter[] { +type QueryContainer = ESFilter; + +export function environmentQuery(environment?: string): QueryContainer[] { if (!environment || environment === ENVIRONMENT_ALL.value) { return []; } + if (environment === ENVIRONMENT_NOT_DEFINED.value) { return [{ bool: { must_not: { exists: { field: SERVICE_ENVIRONMENT } } } }]; } + return [{ term: { [SERVICE_ENVIRONMENT]: environment } }]; } + +export function rangeQuery( + start: number, + end: number, + field = '@timestamp' +): QueryContainer[] { + return [ + { + range: { + [field]: { + gte: start, + lte: end, + format: 'epoch_millis', + }, + }, + }, + ]; +} diff --git a/x-pack/plugins/apm/common/utils/range_filter.ts b/x-pack/plugins/apm/common/utils/range_filter.ts deleted file mode 100644 index 8d5b7d5e1beb1e..00000000000000 --- a/x-pack/plugins/apm/common/utils/range_filter.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export function rangeFilter(start: number, end: number) { - return { - '@timestamp': { - gte: start, - lte: end, - format: 'epoch_millis', - }, - }; -} diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx index 3edf21eae7279f..4cd2db43621a8f 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx @@ -68,7 +68,7 @@ type ErrorGroupDetailsProps = RouteComponentProps<{ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { const { serviceName, groupId } = match.params; const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { data: errorGroupData } = useFetcher( (callApmApi) => { @@ -81,6 +81,7 @@ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { groupId, }, query: { + environment, start, end, uiFilters: JSON.stringify(uiFilters), @@ -89,7 +90,7 @@ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { }); } }, - [serviceName, start, end, groupId, uiFilters] + [environment, serviceName, start, end, groupId, uiFilters] ); const { errorDistributionData } = useErrorGroupDistributionFetcher({ diff --git a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx index 7386209310c1fb..9b80ee6fc31b85 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx @@ -51,7 +51,13 @@ export function ErrorCorrelations({ onClose }: Props) { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); - const { transactionName, transactionType, start, end } = urlParams; + const { + environment, + transactionName, + transactionType, + start, + end, + } = urlParams; const { defaultFieldNames } = useFieldNames(); const [fieldNames, setFieldNames] = useLocalStorage( `apm.correlations.errors.fields:${serviceName}`, @@ -65,6 +71,7 @@ export function ErrorCorrelations({ onClose }: Props) { endpoint: 'GET /api/apm/correlations/failed_transactions', params: { query: { + environment, serviceName, transactionName, transactionType, @@ -78,6 +85,7 @@ export function ErrorCorrelations({ onClose }: Props) { } }, [ + environment, serviceName, start, end, diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx index c88aaa85bb856f..459df99a62f5a2 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx @@ -49,7 +49,13 @@ export function LatencyCorrelations({ onClose }: Props) { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); - const { transactionName, transactionType, start, end } = urlParams; + const { + environment, + transactionName, + transactionType, + start, + end, + } = urlParams; const { defaultFieldNames } = useFieldNames(); const [fieldNames, setFieldNames] = useLocalStorage( `apm.correlations.latency.fields:${serviceName}`, @@ -70,6 +76,7 @@ export function LatencyCorrelations({ onClose }: Props) { endpoint: 'GET /api/apm/correlations/slow_transactions', params: { query: { + environment, serviceName, transactionName, transactionType, @@ -84,6 +91,7 @@ export function LatencyCorrelations({ onClose }: Props) { } }, [ + environment, serviceName, start, end, diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx index 29bdf6467e5447..bde23eddaa44fa 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/index.tsx @@ -28,7 +28,7 @@ interface ErrorGroupOverviewProps { export function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, sortField, sortDirection } = urlParams; + const { environment, start, end, sortField, sortDirection } = urlParams; const { errorDistributionData } = useErrorGroupDistributionFetcher({ serviceName, groupId: undefined, @@ -46,6 +46,7 @@ export function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { serviceName, }, query: { + environment, start, end, sortField, @@ -56,7 +57,7 @@ export function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { }); } }, - [serviceName, start, end, sortField, sortDirection, uiFilters] + [environment, serviceName, start, end, sortField, sortDirection, uiFilters] ); useTrackPageview({ diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index 32bc907f624fbd..cd17ca0ce023d2 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -39,19 +39,24 @@ function useServicesFetcher() { const { urlParams, uiFilters } = useUrlParams(); const { core } = useApmPluginContext(); const upgradeAssistantHref = useUpgradeAssistantHref(); - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { data = initialData, status } = useFetcher( (callApmApi) => { if (start && end) { return callApmApi({ endpoint: 'GET /api/apm/services', params: { - query: { start, end, uiFilters: JSON.stringify(uiFilters) }, + query: { + environment, + start, + end, + uiFilters: JSON.stringify(uiFilters), + }, }, }); } }, - [start, end, uiFilters] + [environment, start, end, uiFilters] ); useEffect(() => { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index baae683e2eba95..2f37e8e4238d8b 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -14,10 +14,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { - ENVIRONMENT_ALL, - getNextEnvironmentUrlParam, -} from '../../../../../common/environment_filter_values'; +import { getNextEnvironmentUrlParam } from '../../../../../common/environment_filter_values'; import { asMillisecondDuration, asPercent, @@ -182,7 +179,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { query: { start, end, - environment: environment || ENVIRONMENT_ALL.value, + environment, numBuckets: 20, }, }, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index 7f4823c13d593a..f7f5db32e986cc 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -52,7 +52,7 @@ const DEFAULT_SORT = { export function ServiceOverviewErrorsTable({ serviceName }: Props) { const { - urlParams: { start, end }, + urlParams: { environment, start, end }, uiFilters, } = useUrlParams(); const { transactionType } = useApmServiceContext(); @@ -152,6 +152,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { params: { path: { serviceName }, query: { + environment, start, end, uiFilters: JSON.stringify(uiFilters), @@ -178,6 +179,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { }); }, [ + environment, start, end, serviceName, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx index e7c1d4442e3b7c..819d65a5d9415e 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx @@ -25,7 +25,7 @@ export function ServiceOverviewInstancesChartAndTable({ const { transactionType } = useApmServiceContext(); const { - urlParams: { start, end }, + urlParams: { environment, start, end }, uiFilters, } = useUrlParams(); @@ -43,6 +43,7 @@ export function ServiceOverviewInstancesChartAndTable({ serviceName, }, query: { + environment, start, end, transactionType, @@ -52,7 +53,7 @@ export function ServiceOverviewInstancesChartAndTable({ }, }); }, - [start, end, serviceName, transactionType, uiFilters] + [environment, start, end, serviceName, transactionType, uiFilters] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx index 1d0074da6005ff..2d38ce2c23ca7c 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx @@ -30,7 +30,7 @@ export function ServiceOverviewThroughputChart({ const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); const { transactionType } = useApmServiceContext(); - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { @@ -42,6 +42,7 @@ export function ServiceOverviewThroughputChart({ serviceName, }, query: { + environment, start, end, transactionType, @@ -51,7 +52,7 @@ export function ServiceOverviewThroughputChart({ }); } }, - [serviceName, start, end, uiFilters, transactionType] + [environment, serviceName, start, end, uiFilters, transactionType] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index a0facb2ddbedf6..5529f9028b9dda 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -58,7 +58,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { const { transactionType } = useApmServiceContext(); const { uiFilters, - urlParams: { start, end, latencyAggregationType }, + urlParams: { environment, start, end, latencyAggregationType }, } = useUrlParams(); const { data = INITIAL_STATE, status } = useFetcher( @@ -72,6 +72,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { params: { path: { serviceName }, query: { + environment, start, end, uiFilters: JSON.stringify(uiFilters), @@ -87,6 +88,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { }); }, [ + environment, serviceName, start, end, @@ -125,6 +127,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { params: { path: { serviceName }, query: { + environment, start, end, uiFilters: JSON.stringify(uiFilters), diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx index cae0ef2de2ad1b..8fc9ac12824baa 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx @@ -23,7 +23,7 @@ const DEFAULT_RESPONSE: TracesAPIResponse = { export function TraceOverview() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { status, data = DEFAULT_RESPONSE } = useFetcher( (callApmApi) => { if (start && end) { @@ -31,6 +31,7 @@ export function TraceOverview() { endpoint: 'GET /api/apm/traces', params: { query: { + environment, start, end, uiFilters: JSON.stringify(uiFilters), @@ -39,7 +40,7 @@ export function TraceOverview() { }); } }, - [start, end, uiFilters] + [environment, start, end, uiFilters] ); useTrackPageview({ app: 'apm', path: 'traces_overview' }); diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts index 406ba98b79e256..a63788457b8b5d 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts @@ -21,7 +21,7 @@ const DEFAULT_RESPONSE: Partial = { export function useTransactionListFetcher() { const { urlParams, uiFilters } = useUrlParams(); const { serviceName } = useParams<{ serviceName?: string }>(); - const { transactionType, start, end } = urlParams; + const { environment, transactionType, start, end } = urlParams; const { data = DEFAULT_RESPONSE, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && transactionType) { @@ -30,6 +30,7 @@ export function useTransactionListFetcher() { params: { path: { serviceName }, query: { + environment, start, end, transactionType, @@ -39,7 +40,7 @@ export function useTransactionListFetcher() { }); } }, - [serviceName, start, end, transactionType, uiFilters] + [environment, serviceName, start, end, transactionType, uiFilters] ); return { diff --git a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx index 714645fd749612..59c99463144cbe 100644 --- a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx @@ -64,10 +64,9 @@ export function EnvironmentFilter() { const history = useHistory(); const location = useLocation(); const { serviceName } = useParams<{ serviceName?: string }>(); - const { uiFilters, urlParams } = useUrlParams(); + const { urlParams } = useUrlParams(); - const { environment } = uiFilters; - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { environments, status = 'loading' } = useEnvironmentsFetcher({ serviceName, start, diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts index 466f201ab3398e..293a1929ca6069 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts @@ -13,7 +13,7 @@ import { useApmServiceContext } from '../../../../context/apm_service/use_apm_se export function useTransactionBreakdown() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); - const { start, end, transactionName } = urlParams; + const { environment, start, end, transactionName } = urlParams; const { transactionType } = useApmServiceContext(); const { data = { timeseries: undefined }, error, status } = useFetcher( @@ -25,6 +25,7 @@ export function useTransactionBreakdown() { params: { path: { serviceName }, query: { + environment, start, end, transactionName, @@ -35,7 +36,15 @@ export function useTransactionBreakdown() { }); } }, - [serviceName, start, end, transactionType, transactionName, uiFilters] + [ + environment, + serviceName, + start, + end, + transactionType, + transactionName, + uiFilters, + ] ); return { diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index df18e7627faedb..a3da8812966f18 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -33,7 +33,7 @@ export function TransactionErrorRateChart({ const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); const { transactionType } = useApmServiceContext(); - const { start, end, transactionName } = urlParams; + const { environment, start, end, transactionName } = urlParams; const { data, status } = useFetcher( (callApmApi) => { @@ -46,6 +46,7 @@ export function TransactionErrorRateChart({ serviceName, }, query: { + environment, start, end, transactionType, @@ -56,7 +57,15 @@ export function TransactionErrorRateChart({ }); } }, - [serviceName, start, end, uiFilters, transactionType, transactionName] + [ + environment, + serviceName, + start, + end, + uiFilters, + transactionType, + transactionName, + ] ); const errorRates = data?.transactionErrorRate || []; diff --git a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts index 63d719205c2ade..b6e7330be30cbd 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts @@ -6,6 +6,7 @@ */ import { Location } from 'history'; +import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; import { pickKeys } from '../../../common/utils/pick_keys'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -66,6 +67,7 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { refreshInterval: refreshInterval ? toNumber(refreshInterval) : undefined, // query params + environment: toString(environment) || ENVIRONMENT_ALL.value, sortDirection, sortField, page: toNumber(page) || 0, @@ -87,7 +89,6 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { : undefined, comparisonType: comparisonType as TimeRangeComparisonType | undefined, // ui filters - environment, ...localUIFilters, }); } diff --git a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx index 8312fedc7eb039..90245b9843b013 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx @@ -14,7 +14,6 @@ import React, { useState, } from 'react'; import { withRouter } from 'react-router-dom'; -import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; import { LocalUIFilterName } from '../../../common/ui_filter'; import { pickKeys } from '../../../common/utils/pick_keys'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -39,7 +38,6 @@ function useUiFilters(params: IUrlParams): UIFilters { return useDeepObjectIdentity({ kuery, - environment: environment || ENVIRONMENT_ALL.value, ...localUiFilters, }); } diff --git a/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx index 834b0cc0527897..9ff179e6af6a06 100644 --- a/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx @@ -16,7 +16,7 @@ export function useErrorGroupDistributionFetcher({ groupId: string | undefined; }) { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { data } = useFetcher( (callApmApi) => { if (start && end) { @@ -25,6 +25,7 @@ export function useErrorGroupDistributionFetcher({ params: { path: { serviceName }, query: { + environment, start, end, groupId, @@ -34,7 +35,7 @@ export function useErrorGroupDistributionFetcher({ }); } }, - [serviceName, start, end, groupId, uiFilters] + [environment, serviceName, start, end, groupId, uiFilters] ); return { errorDistributionData: data }; diff --git a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts index ecfa5471189d20..87e10f1e8937b0 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts @@ -24,7 +24,7 @@ export function useServiceMetricChartsFetcher({ const { urlParams, uiFilters } = useUrlParams(); const { agentName } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); - const { start, end } = urlParams; + const { environment, start, end } = urlParams; const { data = INITIAL_DATA, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && agentName) { @@ -33,6 +33,7 @@ export function useServiceMetricChartsFetcher({ params: { path: { serviceName }, query: { + environment, serviceNodeName, start, end, @@ -43,7 +44,15 @@ export function useServiceMetricChartsFetcher({ }); } }, - [serviceName, start, end, agentName, serviceNodeName, uiFilters] + [ + environment, + serviceName, + start, + end, + agentName, + serviceNodeName, + uiFilters, + ] ); return { diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts index 66446bf0dfebad..c493a30716aa5e 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts @@ -25,6 +25,7 @@ export function useTransactionDistributionFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); const { + environment, start, end, transactionType, @@ -45,6 +46,7 @@ export function useTransactionDistributionFetcher() { serviceName, }, query: { + environment, start, end, transactionType, @@ -92,7 +94,15 @@ export function useTransactionDistributionFetcher() { }, // the histogram should not be refetched if the transactionId or traceId changes // eslint-disable-next-line react-hooks/exhaustive-deps - [serviceName, start, end, transactionType, transactionName, uiFilters] + [ + environment, + serviceName, + start, + end, + transactionType, + transactionName, + uiFilters, + ] ); return { diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts index d5974ee3543a7e..cca2e99d84dfdf 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_latency_chart_fetcher.ts @@ -18,7 +18,13 @@ export function useTransactionLatencyChartsFetcher() { const { transactionType } = useApmServiceContext(); const theme = useTheme(); const { - urlParams: { start, end, transactionName, latencyAggregationType }, + urlParams: { + environment, + start, + end, + transactionName, + latencyAggregationType, + }, uiFilters, } = useUrlParams(); @@ -37,6 +43,7 @@ export function useTransactionLatencyChartsFetcher() { params: { path: { serviceName }, query: { + environment, start, end, transactionType, @@ -49,6 +56,7 @@ export function useTransactionLatencyChartsFetcher() { } }, [ + environment, serviceName, start, end, diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts index af9a5fee24877a..55765cd40c04eb 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_throughput_chart_fetcher.ts @@ -18,7 +18,7 @@ export function useTransactionThroughputChartsFetcher() { const { transactionType } = useApmServiceContext(); const theme = useTheme(); const { - urlParams: { start, end, transactionName }, + urlParams: { environment, start, end, transactionName }, uiFilters, } = useUrlParams(); @@ -31,6 +31,7 @@ export function useTransactionThroughputChartsFetcher() { params: { path: { serviceName }, query: { + environment, start, end, transactionType, @@ -41,7 +42,15 @@ export function useTransactionThroughputChartsFetcher() { }); } }, - [serviceName, start, end, transactionName, transactionType, uiFilters] + [ + environment, + serviceName, + start, + end, + transactionName, + transactionType, + uiFilters, + ] ); const memoizedData = useMemo( diff --git a/x-pack/plugins/apm/public/utils/testHelpers.tsx b/x-pack/plugins/apm/public/utils/testHelpers.tsx index aac6196c4253c5..f7f6f7486091b7 100644 --- a/x-pack/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/plugins/apm/public/utils/testHelpers.tsx @@ -182,8 +182,8 @@ export async function inspectSearchParams( }, } ) as APMConfig, - uiFilters: { environment: 'test' }, - esFilter: [{ term: { 'service.environment': 'test' } }], + uiFilters: {}, + esFilter: [], indices: { /* eslint-disable @typescript-eslint/naming-convention */ 'apm_oss.sourcemapIndices': 'myIndex', diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts index e487684909633e..3457207eeee3c4 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts @@ -13,10 +13,9 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { AlertParams } from '../../../routes/alerts/chart_preview'; import { withApmSpan } from '../../../utils/with_apm_span'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; @@ -39,13 +38,13 @@ export function getTransactionDurationChartPreview({ const query = { bool: { filter: [ - { range: rangeFilter(start, end) }, { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), ...(transactionType ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] : []), - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }; diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts index 05ad743af0997b..aa85c44284d9d4 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts @@ -7,10 +7,9 @@ import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeFilter } from '../../../../common/utils/range_filter'; import { AlertParams } from '../../../routes/alerts/chart_preview'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { withApmSpan } from '../../../utils/with_apm_span'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; @@ -28,9 +27,9 @@ export function getTransactionErrorCountChartPreview({ const query = { bool: { filter: [ - { range: rangeFilter(start, end) }, ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }; diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts index fa01773c780703..88e249a71a81f0 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts @@ -11,9 +11,8 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeFilter } from '../../../../common/utils/range_filter'; import { AlertParams } from '../../../routes/alerts/chart_preview'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { @@ -34,13 +33,13 @@ export async function getTransactionErrorRateChartPreview({ const query = { bool: { filter: [ - { range: rangeFilter(start, end) }, { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), ...(transactionType ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] : []), - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts index e9e2e078ec344e..c7861eaa819aed 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts @@ -27,7 +27,7 @@ import { SERVICE_NAME, } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery } from '../../../common/utils/queries'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from './action_variables'; import { alertingEsClient } from './alerting_es_client'; @@ -104,7 +104,7 @@ export function registerErrorCountAlertType({ ...(alertParams.serviceName ? [{ term: { [SERVICE_NAME]: alertParams.serviceName } }] : []), - ...getEnvironmentUiFilterES(alertParams.environment), + ...environmentQuery(alertParams.environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index 480efd8d4c7ad0..704aee932a604f 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -20,7 +20,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; import { getDurationFormatter } from '../../../common/utils/formatters'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery } from '../../../common/utils/queries'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from './action_variables'; import { alertingEsClient } from './alerting_es_client'; @@ -96,7 +96,7 @@ export function registerTransactionDurationAlertType({ { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, { term: { [SERVICE_NAME]: alertParams.serviceName } }, { term: { [TRANSACTION_TYPE]: alertParams.transactionType } }, - ...getEnvironmentUiFilterES(alertParams.environment), + ...environmentQuery(alertParams.environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts index 882bde87927615..6f58b7714d8324 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts @@ -22,7 +22,7 @@ import { import { EventOutcome } from '../../../common/event_outcome'; import { ProcessorEvent } from '../../../common/processor_event'; import { asDecimalOrInteger } from '../../../common/utils/formatters'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery } from '../../../common/utils/queries'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { apmActionVariables } from './action_variables'; import { alertingEsClient } from './alerting_es_client'; @@ -103,7 +103,7 @@ export function registerTransactionErrorRateAlertType({ }, ] : []), - ...getEnvironmentUiFilterES(alertParams.environment), + ...environmentQuery(alertParams.environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts index a560f011b186bb..d70e19bf4a5f52 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts @@ -9,15 +9,15 @@ import { Logger } from 'kibana/server'; import uuid from 'uuid/v4'; import { snakeCase } from 'lodash'; import Boom from '@hapi/boom'; -import { ProcessorEvent } from '../../../common/processor_event'; import { ML_ERRORS } from '../../../common/anomaly_detection'; +import { ProcessorEvent } from '../../../common/processor_event'; +import { environmentQuery } from '../../../common/utils/queries'; import { Setup } from '../helpers/setup_request'; import { TRANSACTION_DURATION, PROCESSOR_EVENT, } from '../../../common/elasticsearch_fieldnames'; import { APM_ML_JOB_GROUP, ML_MODULE_ID_APM_TRANSACTION } from './constants'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { withApmSpan } from '../../utils/with_apm_span'; export async function createAnomalyDetectionJobs( @@ -86,7 +86,7 @@ async function createAnomalyDetectionJob({ filter: [ { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, { exists: { field: TRANSACTION_DURATION } }, - ...getEnvironmentUiFilterES(environment), + ...environmentQuery(environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts index 721e35e2ef60d0..ecefdfc2b3d9b2 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts @@ -13,7 +13,7 @@ import { } from '../process_significant_term_aggs'; import { AggregationOptionsByType } from '../../../../../../typings/elasticsearch/aggregations'; import { ESFilter } from '../../../../../../typings/elasticsearch'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { EVENT_OUTCOME, SERVICE_NAME, @@ -31,12 +31,14 @@ import { import { withApmSpan } from '../../../utils/with_apm_span'; export async function getCorrelationsForFailedTransactions({ + environment, serviceName, transactionType, transactionName, fieldNames, setup, }: { + environment?: string; serviceName: string | undefined; transactionType: string | undefined; transactionName: string | undefined; @@ -47,9 +49,10 @@ export async function getCorrelationsForFailedTransactions({ const { start, end, esFilter, apmEventClient } = setup; const backgroundFilters: ESFilter[] = [ - ...esFilter, - { range: rangeFilter(start, end) }, { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ]; if (serviceName) { diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts index 816061da5cfc17..832b89a18d1028 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts @@ -7,7 +7,7 @@ import { AggregationOptionsByType } from '../../../../../../typings/elasticsearch/aggregations'; import { ESFilter } from '../../../../../../typings/elasticsearch'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { SERVICE_NAME, TRANSACTION_DURATION, @@ -23,6 +23,7 @@ import { getLatencyDistribution } from './get_latency_distribution'; import { withApmSpan } from '../../../utils/with_apm_span'; export async function getCorrelationsForSlowTransactions({ + environment, serviceName, transactionType, transactionName, @@ -30,6 +31,7 @@ export async function getCorrelationsForSlowTransactions({ fieldNames, setup, }: { + environment?: string; serviceName: string | undefined; transactionType: string | undefined; transactionName: string | undefined; @@ -41,9 +43,10 @@ export async function getCorrelationsForSlowTransactions({ const { start, end, esFilter, apmEventClient } = setup; const backgroundFilters: ESFilter[] = [ - ...esFilter, - { range: rangeFilter(start, end) }, { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ]; if (serviceName) { diff --git a/x-pack/plugins/apm/server/lib/environments/get_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_environments.ts index 56f0a03910c1a4..af88493c148ce1 100644 --- a/x-pack/plugins/apm/server/lib/environments/get_environments.ts +++ b/x-pack/plugins/apm/server/lib/environments/get_environments.ts @@ -5,14 +5,13 @@ * 2.0. */ -import { ESFilter } from '../../../../../typings/elasticsearch'; import { SERVICE_ENVIRONMENT, SERVICE_NAME, } from '../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; import { ProcessorEvent } from '../../../common/processor_event'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; @@ -37,7 +36,7 @@ export async function getEnvironments({ return withApmSpan(spanName, async () => { const { start, end, apmEventClient, config } = setup; - const filter: ESFilter[] = [{ range: rangeFilter(start, end) }]; + const filter = rangeQuery(start, end); if (serviceName) { filter.push({ diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts index db8414864f5771..1712699162b73c 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts @@ -25,6 +25,7 @@ describe('get buckets', () => { }); await getBuckets({ + environment: 'prod', serviceName: 'myServiceName', bucketSize: 10, setup: { @@ -42,14 +43,8 @@ describe('get buckets', () => { get: () => 'myIndex', } ) as APMConfig, - uiFilters: { - environment: 'prod', - }, - esFilter: [ - { - term: { 'service.environment': 'prod' }, - }, - ], + uiFilters: {}, + esFilter: [], indices: { /* eslint-disable @typescript-eslint/naming-convention */ 'apm_oss.sourcemapIndices': 'apm-*', diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts index 383fcbb2f5ce72..fbe406d8d1a9d2 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -11,16 +11,18 @@ import { SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; export async function getBuckets({ + environment, serviceName, groupId, bucketSize, setup, }: { + environment?: string; serviceName: string; groupId?: string; bucketSize: number; @@ -30,7 +32,8 @@ export async function getBuckets({ const { start, end, esFilter, apmEventClient } = setup; const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ]; diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts index be3a29780a5b65..1fb0cbad4a5f09 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_distribution.ts @@ -14,16 +14,19 @@ function getBucketSize({ start, end }: SetupTimeRange) { } export async function getErrorDistribution({ + environment, serviceName, groupId, setup, }: { + environment?: string; serviceName: string; groupId?: string; setup: Setup & SetupTimeRange; }) { const bucketSize = getBucketSize({ start: setup.start, end: setup.end }); const { buckets, noHits } = await getBuckets({ + environment, serviceName, groupId, bucketSize, diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts b/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts index 121b9b3d0c46fb..0ab26f3c6e969d 100644 --- a/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts +++ b/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts @@ -11,16 +11,18 @@ import { TRANSACTION_SAMPLED, } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getTransaction } from '../transactions/get_transaction'; export function getErrorGroupSample({ + environment, serviceName, groupId, setup, }: { + environment?: string; serviceName: string; groupId: string; setup: Setup & SetupTimeRange; @@ -39,7 +41,8 @@ export function getErrorGroupSample({ filter: [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [ERROR_GROUP_ID]: groupId } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], should: [{ term: { [TRANSACTION_SAMPLED]: true } }], diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts index 6e91f8fe7cdd28..28d89eb0574709 100644 --- a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts +++ b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts @@ -21,11 +21,13 @@ import { getErrorName } from '../helpers/get_error_name'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; export function getErrorGroups({ + environment, serviceName, sortField, sortDirection = 'desc', setup, }: { + environment?: string; serviceName: string; sortField?: string; sortDirection?: 'asc' | 'desc'; @@ -37,7 +39,11 @@ export function getErrorGroups({ // sort buckets by last occurrence of error const sortByLatestOccurrence = sortField === 'latestOccurrenceAt'; - const projection = getErrorGroupsProjection({ setup, serviceName }); + const projection = getErrorGroupsProjection({ + environment, + setup, + serviceName, + }); const order: SortOptions = sortByLatestOccurrence ? { diff --git a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts index 424c85d55a36e1..71744c3e590929 100644 --- a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts @@ -6,7 +6,7 @@ */ import { SearchAggregatedTransactionSetting } from '../../../../common/aggregated_transactions'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { rangeQuery } from '../../../../common/utils/queries'; import { ProcessorEvent } from '../../../../common/processor_event'; import { TRANSACTION_DURATION, @@ -35,7 +35,7 @@ export async function getHasAggregatedTransactions({ bool: { filter: [ { exists: { field: TRANSACTION_DURATION_HISTOGRAM } }, - ...(start && end ? [{ range: rangeFilter(start, end) }] : []), + ...(start && end ? rangeQuery(start, end) : []), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.test.ts b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.test.ts deleted file mode 100644 index 57bf511f459428..00000000000000 --- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_environment_ui_filter_es.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getEnvironmentUiFilterES } from './get_environment_ui_filter_es'; -import { ENVIRONMENT_NOT_DEFINED } from '../../../../common/environment_filter_values'; -import { SERVICE_ENVIRONMENT } from '../../../../common/elasticsearch_fieldnames'; - -describe('getEnvironmentUiFilterES', () => { - it('should return empty array, when environment is undefined', () => { - const uiFilterES = getEnvironmentUiFilterES(); - expect(uiFilterES).toHaveLength(0); - }); - - it('should create a filter for a service environment', () => { - const uiFilterES = getEnvironmentUiFilterES('test'); - expect(uiFilterES).toHaveLength(1); - expect(uiFilterES[0]).toHaveProperty(['term', SERVICE_ENVIRONMENT], 'test'); - }); - - it('should create a filter for missing service environments', () => { - const uiFilterES = getEnvironmentUiFilterES(ENVIRONMENT_NOT_DEFINED.value); - expect(uiFilterES).toHaveLength(1); - expect(uiFilterES[0]).toHaveProperty( - ['bool', 'must_not', 'exists', 'field'], - SERVICE_ENVIRONMENT - ); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts index 63e0edd6097bf4..e91c9b52deecf1 100644 --- a/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts +++ b/x-pack/plugins/apm/server/lib/helpers/convert_ui_filters/get_es_filter.ts @@ -7,7 +7,6 @@ import { ESFilter } from '../../../../../../typings/elasticsearch'; import { UIFilters } from '../../../../typings/ui_filters'; -import { getEnvironmentUiFilterES } from './get_environment_ui_filter_es'; import { localUIFilters, localUIFilterNames, @@ -28,10 +27,7 @@ export function getEsFilter(uiFilters: UIFilters) { }; }) as ESFilter[]; - const esFilters = [ - ...getKueryUiFilterES(uiFilters.kuery), - ...getEnvironmentUiFilterES(uiFilters.environment), - ].concat(mappedFilters) as ESFilter[]; + const esFilters = [...getKueryUiFilterES(uiFilters.kuery), ...mappedFilters]; return esFilters; } diff --git a/x-pack/plugins/apm/server/lib/metrics/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/metrics/__snapshots__/queries.test.ts.snap index 961a1eee61d1d5..4eed09f3e5c28b 100644 --- a/x-pack/plugins/apm/server/lib/metrics/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/metrics/__snapshots__/queries.test.ts.snap @@ -71,6 +71,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -80,11 +85,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -159,6 +159,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -168,11 +173,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -322,6 +322,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -331,11 +336,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -415,6 +415,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -424,11 +429,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -498,6 +498,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -507,11 +512,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -601,15 +601,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -621,6 +612,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", @@ -695,15 +695,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -715,6 +706,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", @@ -864,15 +864,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -884,6 +875,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", @@ -963,15 +963,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -983,6 +974,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", @@ -1052,15 +1052,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -1072,6 +1063,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/default.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/default.ts index e03be2391597d4..c5e80600b69d43 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/default.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/default.ts @@ -9,13 +9,18 @@ import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getCPUChartData } from './shared/cpu'; import { getMemoryChartData } from './shared/memory'; -export async function getDefaultMetricsCharts( - setup: Setup & SetupTimeRange, - serviceName: string -) { +export async function getDefaultMetricsCharts({ + environment, + serviceName, + setup, +}: { + environment?: string; + serviceName: string; + setup: Setup & SetupTimeRange; +}) { const charts = await Promise.all([ - getCPUChartData({ setup, serviceName }), - getMemoryChartData({ setup, serviceName }), + getCPUChartData({ environment, setup, serviceName }), + getMemoryChartData({ environment, setup, serviceName }), ]); return { charts }; diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts index e76ad10b535b09..d7c9294c8ec7a1 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts @@ -22,12 +22,14 @@ import { getBucketSize } from '../../../../helpers/get_bucket_size'; import { getVizColorForIndex } from '../../../../../../common/viz_colors'; export async function fetchAndTransformGcMetrics({ + environment, setup, serviceName, serviceNodeName, chartBase, fieldName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; @@ -39,6 +41,7 @@ export async function fetchAndTransformGcMetrics({ const { bucketSize } = getBucketSize({ start, end }); const projection = getMetricsProjection({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts index 7989f57046ae78..8c5b9fb3db9221 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts @@ -33,16 +33,19 @@ const chartBase: ChartBase = { }; function getGcRateChart({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_gc_rate_charts', () => fetchAndTransformGcMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts index 446894f82b75e3..98f31f06c1b644 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts @@ -33,16 +33,19 @@ const chartBase: ChartBase = { }; function getGcTimeChart({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_gc_time_charts', () => fetchAndTransformGcMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts index 2b7bb9ea8da6ed..d6cbc4a07e8f9b 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts @@ -53,16 +53,19 @@ const chartBase: ChartBase = { }; export function getHeapMemoryChart({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_heap_memory_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts index e137720000262a..970b4d3499b798 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts @@ -16,23 +16,30 @@ import { getGcRateChart } from './gc/get_gc_rate_chart'; import { getGcTimeChart } from './gc/get_gc_time_chart'; export function getJavaMetricsCharts({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_java_system_metric_charts', async () => { const charts = await Promise.all([ - getCPUChartData({ setup, serviceName, serviceNodeName }), - getMemoryChartData({ setup, serviceName, serviceNodeName }), - getHeapMemoryChart({ setup, serviceName, serviceNodeName }), - getNonHeapMemoryChart({ setup, serviceName, serviceNodeName }), - getThreadCountChart({ setup, serviceName, serviceNodeName }), - getGcRateChart({ setup, serviceName, serviceNodeName }), - getGcTimeChart({ setup, serviceName, serviceNodeName }), + getCPUChartData({ environment, setup, serviceName, serviceNodeName }), + getMemoryChartData({ environment, setup, serviceName, serviceNodeName }), + getHeapMemoryChart({ environment, setup, serviceName, serviceNodeName }), + getNonHeapMemoryChart({ + environment, + setup, + serviceName, + serviceNodeName, + }), + getThreadCountChart({ environment, setup, serviceName, serviceNodeName }), + getGcRateChart({ environment, setup, serviceName, serviceNodeName }), + getGcTimeChart({ environment, setup, serviceName, serviceNodeName }), ]); return { charts }; diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts index a3e253d2c81d67..25abd2c34c83a9 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts @@ -50,16 +50,19 @@ const chartBase: ChartBase = { }; export async function getNonHeapMemoryChart({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_non_heap_memory_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts index e176c156ad05a5..c8a209fee701a1 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts @@ -42,16 +42,19 @@ const chartBase: ChartBase = { }; export async function getThreadCountChart({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_thread_count_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts index e7f576b73c5aef..ebfe504e5269b7 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts @@ -54,16 +54,19 @@ const chartBase: ChartBase = { }; export function getCPUChartData({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; }) { return withApmSpan('get_cpu_metric_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts index 0f7954d86d3e2c..55b3328bcd2a9b 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts @@ -71,10 +71,12 @@ export const percentCgroupMemoryUsedScript = { }; export async function getMemoryChartData({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; @@ -84,6 +86,7 @@ export async function getMemoryChartData({ 'get_cgroup_memory_metrics_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, @@ -101,6 +104,7 @@ export async function getMemoryChartData({ if (cgroupResponse.noHits) { return await withApmSpan('get_system_memory_metrics_charts', () => fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts index 7a52806601e0e8..17e9aef29ba82d 100644 --- a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts +++ b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts @@ -48,6 +48,7 @@ interface Filter { } export async function fetchAndTransformMetrics({ + environment, setup, serviceName, serviceNodeName, @@ -55,6 +56,7 @@ export async function fetchAndTransformMetrics({ aggs, additionalFilters = [], }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; @@ -65,6 +67,7 @@ export async function fetchAndTransformMetrics({ const { start, end, apmEventClient, config } = setup; const projection = getMetricsProjection({ + environment, setup, serviceName, serviceNodeName, diff --git a/x-pack/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts b/x-pack/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts index 5083982f1cb9cb..eda71ef380ee9c 100644 --- a/x-pack/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts +++ b/x-pack/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts @@ -15,11 +15,13 @@ export interface MetricsChartsByAgentAPIResponse { } export async function getMetricsChartDataByAgent({ + environment, setup, serviceName, serviceNodeName, agentName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; @@ -27,11 +29,16 @@ export async function getMetricsChartDataByAgent({ }): Promise { switch (agentName) { case 'java': { - return getJavaMetricsCharts({ setup, serviceName, serviceNodeName }); + return getJavaMetricsCharts({ + environment, + setup, + serviceName, + serviceNodeName, + }); } default: { - return getDefaultMetricsCharts(setup, serviceName); + return getDefaultMetricsCharts({ environment, setup, serviceName }); } } } diff --git a/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts b/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts index 4af57a685bf839..c7ac678899b58c 100644 --- a/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts +++ b/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts @@ -6,7 +6,7 @@ */ import { ProcessorEvent } from '../../../common/processor_event'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; @@ -36,7 +36,7 @@ export function getServiceCount({ size: 0, query: { bool: { - filter: [{ range: rangeFilter(start, end) }], + filter: rangeQuery(start, end), }, }, aggs: { serviceCount: { cardinality: { field: SERVICE_NAME } } }, diff --git a/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts b/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts index 87394567afc50b..2da4b0f8de3632 100644 --- a/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts +++ b/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { Coordinates } from '../../../../observability/typings/common'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; @@ -38,7 +38,7 @@ export function getTransactionCoordinates({ size: 0, query: { bool: { - filter: [{ range: rangeFilter(start, end) }], + filter: rangeQuery(start, end), }, }, aggs: { diff --git a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts index 368c5ec5463592..9626019347e5bb 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts @@ -11,7 +11,7 @@ import { TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { TRANSACTION_PAGE_LOAD } from '../../../common/transaction_types'; export async function hasRumData({ setup }: { setup: Setup & SetupTimeRange }) { @@ -31,9 +31,7 @@ export async function hasRumData({ setup }: { setup: Setup & SetupTimeRange }) { }, aggs: { services: { - filter: { - range: rangeFilter(start, end), - }, + filter: rangeQuery(start, end)[0], aggs: { mostTraffic: { terms: { diff --git a/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts index 939ebbb1f79419..259a0e6daea6fc 100644 --- a/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { ProcessorEvent } from '../../../common/processor_event'; import { TRACE_ID } from '../../../common/elasticsearch_fieldnames'; import { @@ -42,7 +42,7 @@ export async function fetchServicePathsFromTraceIds( [TRACE_ID]: traceIds, }, }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index 2c64678eb082ec..ab221e30ea4897 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -17,6 +17,7 @@ import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, } from '../../../common/transaction_types'; +import { rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; import { getMlJobsWithAPMGroup } from '../anomaly_detection/get_ml_jobs_with_apm_group'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; @@ -51,16 +52,11 @@ export async function getServiceAnomalies({ bool: { filter: [ { terms: { result_type: ['model_plot', 'record'] } }, - { - range: { - timestamp: { - // fetch data for at least 30 minutes - gte: Math.min(end - 30 * 60 * 1000, start), - lte: end, - format: 'epoch_millis', - }, - }, - }, + ...rangeQuery( + Math.min(end - 30 * 60 * 1000, start), + end, + 'timestamp' + ), { terms: { // Only retrieving anomalies for transaction types "request" and "page-load" diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts index 951484308db195..1aee1bb5b242af 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts @@ -15,8 +15,8 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { getServicesProjection } from '../../projections/services'; import { mergeProjection } from '../../projections/util/merge_projection'; +import { environmentQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { DEFAULT_ANOMALIES, @@ -88,14 +88,17 @@ async function getConnectionData({ async function getServicesData(options: IEnvOptions) { return withApmSpan('get_service_stats_for_service_map', async () => { - const { setup, searchAggregatedTransactions } = options; + const { environment, setup, searchAggregatedTransactions } = options; const projection = getServicesProjection({ setup: { ...setup, esFilter: [] }, searchAggregatedTransactions, }); - let { filter } = projection.body.query.bool; + let filter = [ + ...projection.body.query.bool.filter, + ...environmentQuery(environment), + ]; if (options.serviceName) { filter = filter.concat({ @@ -105,10 +108,6 @@ async function getServicesData(options: IEnvOptions) { }); } - if (options.environment) { - filter = filter.concat(getEnvironmentUiFilterES(options.environment)); - } - const params = mergeProjection(projection, { body: { size: 0, diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts index ef4faf94063461..6e9225041b199c 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts @@ -6,7 +6,10 @@ */ import { find, uniqBy } from 'lodash'; -import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; +import { + ENVIRONMENT_ALL, + ENVIRONMENT_NOT_DEFINED, +} from '../../../common/environment_filter_values'; import { SERVICE_ENVIRONMENT, SERVICE_NAME, @@ -27,8 +30,10 @@ export function getConnections({ if (!paths) { return []; } + const isEnvironmentSelected = + environment && environment !== ENVIRONMENT_ALL.value; - if (serviceName || environment) { + if (serviceName || isEnvironmentSelected) { paths = paths.filter((path) => { return ( path diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts index 63f28abab8f3aa..b161345e729d35 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts @@ -19,11 +19,13 @@ describe('getServiceMapServiceNodeInfo', () => { hits: { total: { value: 0 } }, }), }, + esFilter: [], indices: {}, - uiFilters: { environment: 'test environment' }, + uiFilters: {}, } as unknown) as Setup & SetupTimeRange; const serviceName = 'test service name'; const result = await getServiceMapServiceNodeInfo({ + environment: 'test environment', setup, serviceName, searchAggregatedTransactions: false, diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts index 213702bf06f4ce..e384b15685dad4 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts @@ -19,14 +19,13 @@ import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, } from '../../../common/transaction_types'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, getTransactionDurationFieldForAggregatedTransactions, } from '../helpers/aggregated_transactions'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { percentCgroupMemoryUsedScript, @@ -51,6 +50,7 @@ interface TaskParameters { } export function getServiceMapServiceNodeInfo({ + environment, serviceName, setup, searchAggregatedTransactions, @@ -59,9 +59,9 @@ export function getServiceMapServiceNodeInfo({ const { start, end, uiFilters } = setup; const filter: ESFilter[] = [ - { range: rangeFilter(start, end) }, { term: { [SERVICE_NAME]: serviceName } }, - ...getEnvironmentUiFilterES(uiFilters.environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ]; const minutes = Math.abs((end - start) / (1000 * 60)); @@ -106,16 +106,13 @@ async function getErrorStats({ searchAggregatedTransactions: boolean; }) { return withApmSpan('get_error_rate_for_service_map_node', async () => { - const setupWithBlankUiFilters = { - ...setup, - uiFilters: { environment }, - esFilter: getEnvironmentUiFilterES(environment), - }; const { noHits, average } = await getErrorRate({ - setup: setupWithBlankUiFilters, + environment, + setup, serviceName, searchAggregatedTransactions, }); + return { avgErrorRate: noHits ? null : average }; }); } diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts index deb9104a839051..e8dcb28baa9a38 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts @@ -16,9 +16,8 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../common/processor_event'; import { SERVICE_MAP_TIMEOUT_ERROR } from '../../../common/service_map'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; -import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; const MAX_TRACES_TO_INSPECT = 1000; @@ -35,8 +34,6 @@ export function getTraceSampleIds({ return withApmSpan('get_trace_sample_ids', async () => { const { start, end, apmEventClient, config } = setup; - const rangeQuery = { range: rangeFilter(start, end) }; - const query = { bool: { filter: [ @@ -45,7 +42,7 @@ export function getTraceSampleIds({ field: SPAN_DESTINATION_SERVICE_RESOURCE, }, }, - rangeQuery, + ...rangeQuery(start, end), ] as ESFilter[], }, } as { bool: { filter: ESFilter[]; must_not?: ESFilter[] | ESFilter } }; @@ -54,7 +51,7 @@ export function getTraceSampleIds({ query.bool.filter.push({ term: { [SERVICE_NAME]: serviceName } }); } - query.bool.filter.push(...getEnvironmentUiFilterES(environment)); + query.bool.filter.push(...environmentQuery(environment)); const fingerprintBucketSize = serviceName ? config['xpack.apm.serviceMapFingerprintBucketSize'] diff --git a/x-pack/plugins/apm/server/lib/service_nodes/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/service_nodes/__snapshots__/queries.test.ts.snap index d83e558775be40..e6d702cc03c0b1 100644 --- a/x-pack/plugins/apm/server/lib/service_nodes/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/service_nodes/__snapshots__/queries.test.ts.snap @@ -35,6 +35,11 @@ Object { "service.name": "foo", }, }, + Object { + "term": Object { + "service.node.name": "bar", + }, + }, Object { "range": Object { "@timestamp": Object { @@ -44,11 +49,6 @@ Object { }, }, }, - Object { - "term": Object { - "service.node.name": "bar", - }, - }, Object { "term": Object { "service.environment": "test", @@ -97,15 +97,6 @@ Object { "service.name": "foo", }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, Object { "bool": Object { "must_not": Array [ @@ -117,6 +108,15 @@ Object { ], }, }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, Object { "term": Object { "service.environment": "test", diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts index 7c746aac29af92..67aa9d7fcd8707 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts @@ -12,13 +12,12 @@ import { SERVICE_NAME, SERVICE_VERSION, } from '../../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { withApmSpan } from '../../../utils/with_apm_span'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, } from '../../helpers/aggregated_transactions'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; export async function getDerivedServiceAnnotations({ @@ -40,7 +39,7 @@ export async function getDerivedServiceAnnotations({ ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), - ...getEnvironmentUiFilterES(environment), + ...environmentQuery(environment), ]; const versions = @@ -57,7 +56,7 @@ export async function getDerivedServiceAnnotations({ size: 0, query: { bool: { - filter: [...filter, { range: rangeFilter(start, end) }], + filter: [...filter, ...rangeQuery(start, end)], }, }, aggs: { diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts index fc53f763dac0ff..6c7cbc26ea6536 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts @@ -5,19 +5,18 @@ * 2.0. */ -import { ElasticsearchClient, Logger } from 'kibana/server'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; +import { ElasticsearchClient, Logger } from 'kibana/server'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { unwrapEsResponse, WrappedElasticsearchClientError, } from '../../../../../observability/server'; -import { rangeFilter } from '../../../../common/utils/range_filter'; import { ESSearchResponse } from '../../../../../../typings/elasticsearch'; import { Annotation as ESAnnotation } from '../../../../../observability/common/annotations'; import { ScopedAnnotationsClient } from '../../../../../observability/server'; import { Annotation, AnnotationType } from '../../../../common/annotations'; import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { withApmSpan } from '../../../utils/with_apm_span'; @@ -37,18 +36,18 @@ export function getStoredAnnotations({ logger: Logger; }): Promise { return withApmSpan('get_stored_annotations', async () => { + const { start, end } = setup; + const body = { size: 50, query: { bool: { filter: [ - { - range: rangeFilter(setup.start, setup.end), - }, { term: { 'annotation.type': 'deployment' } }, { term: { tags: 'apm' } }, { term: { [SERVICE_NAME]: serviceName } }, - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts b/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts index 29c77da6e4075f..3683a069342a9b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts @@ -10,7 +10,7 @@ import { AGENT_NAME, SERVICE_NAME, } from '../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { withApmSpan } from '../../utils/with_apm_span'; @@ -44,7 +44,7 @@ export function getServiceAgentName({ bool: { filter: [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts index aa53e8da6cad05..558d6ae22f00f5 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts @@ -19,9 +19,8 @@ import { SPAN_SUBTYPE, SPAN_TYPE, } from '../../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../../common/utils/range_filter'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { joinByKey } from '../../../../common/utils/join_by_key'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { withApmSpan } from '../../../utils/with_apm_span'; @@ -33,7 +32,7 @@ export const getDestinationMap = ({ }: { setup: Setup & SetupTimeRange; serviceName: string; - environment: string; + environment?: string; }) => { return withApmSpan('get_service_destination_map', async () => { const { start, end, apmEventClient } = setup; @@ -50,8 +49,8 @@ export const getDestinationMap = ({ filter: [ { term: { [SERVICE_NAME]: serviceName } }, { exists: { field: SPAN_DESTINATION_SERVICE_RESOURCE } }, - { range: rangeFilter(start, end) }, - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }, @@ -122,7 +121,7 @@ export const getDestinationMap = ({ ), }, }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts index 9a020daa7e095d..dfbdfb3f504e8c 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts @@ -13,9 +13,8 @@ import { SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT, SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM, } from '../../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../../common/utils/range_filter'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { EventOutcome } from '../../../../common/event_outcome'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; @@ -29,7 +28,7 @@ export const getMetrics = ({ }: { setup: Setup & SetupTimeRange; serviceName: string; - environment: string; + environment?: string; numBuckets: number; }) => { return withApmSpan('get_service_destination_metrics', async () => { @@ -49,8 +48,8 @@ export const getMetrics = ({ { exists: { field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT }, }, - { range: rangeFilter(start, end) }, - ...getEnvironmentUiFilterES(environment), + ...rangeQuery(start, end), + ...environmentQuery(environment), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts index 19f306f5cb8035..724b5278d7edfc 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts @@ -51,7 +51,7 @@ export function getServiceDependencies({ }: { serviceName: string; setup: Setup & SetupTimeRange; - environment: string; + environment?: string; numBuckets: number; }): Promise { return withApmSpan('get_service_dependencies', async () => { diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts index f5aa01e1dfa58a..a17fb6da2007fa 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts @@ -9,7 +9,7 @@ import { ValuesType } from 'utility-types'; import { orderBy } from 'lodash'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; import { PromiseReturnType } from '../../../../../observability/typings/common'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { ProcessorEvent } from '../../../../common/processor_event'; import { ERROR_EXC_MESSAGE, @@ -28,6 +28,7 @@ export type ServiceErrorGroupItem = ValuesType< >; export async function getServiceErrorGroups({ + environment, serviceName, setup, size, @@ -37,6 +38,7 @@ export async function getServiceErrorGroups({ sortField, transactionType, }: { + environment?: string; serviceName: string; setup: Setup & SetupTimeRange; size: number; @@ -63,7 +65,8 @@ export async function getServiceErrorGroups({ filter: [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, @@ -145,7 +148,8 @@ export async function getServiceErrorGroups({ { terms: { [ERROR_GROUP_ID]: sortedErrorGroupIds } }, { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts index 4f8088352d0ae2..ef90e5197229b9 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts @@ -6,7 +6,7 @@ */ import { AggregationOptionsByType } from '../../../../../../typings/elasticsearch'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; import { METRIC_CGROUP_MEMORY_USAGE_BYTES, @@ -26,6 +26,7 @@ import { import { withApmSpan } from '../../../utils/with_apm_span'; export async function getServiceInstanceSystemMetricStats({ + environment, setup, serviceName, size, @@ -95,8 +96,9 @@ export async function getServiceInstanceSystemMetricStats({ query: { bool: { filter: [ - { range: rangeFilter(start, end) }, { term: { [SERVICE_NAME]: serviceName } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], should: [cgroupMemoryFilter, systemMemoryFilter, cpuUsageFilter], diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts index 2cbe5a42206d18..b56625bcebc998 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts @@ -6,7 +6,7 @@ */ import { EventOutcome } from '../../../../common/event_outcome'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; import { EVENT_OUTCOME, @@ -24,6 +24,7 @@ import { calculateThroughput } from '../../helpers/calculate_throughput'; import { withApmSpan } from '../../../utils/with_apm_span'; export async function getServiceInstanceTransactionStats({ + environment, setup, transactionType, serviceName, @@ -72,9 +73,10 @@ export async function getServiceInstanceTransactionStats({ query: { bool: { filter: [ - { range: rangeFilter(start, end) }, { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts index 021774f9522c1b..4c16940e6d2538 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts @@ -12,6 +12,7 @@ import { getServiceInstanceSystemMetricStats } from './get_service_instance_syst import { getServiceInstanceTransactionStats } from './get_service_instance_transaction_stats'; export interface ServiceInstanceParams { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; transactionType: string; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts index f1198a4d858fda..5c43191cf588c2 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts @@ -21,7 +21,7 @@ import { SERVICE_VERSION, } from '../../../common/elasticsearch_fieldnames'; import { ContainerType } from '../../../common/service_metadata'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; @@ -74,7 +74,7 @@ export function getServiceMetadataDetails({ const filter = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ]; const params = { diff --git a/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts b/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts index 0ea95a08abaa98..b342ffea02464e 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts @@ -16,7 +16,7 @@ import { HOST_OS_PLATFORM, } from '../../../common/elasticsearch_fieldnames'; import { ContainerType } from '../../../common/service_metadata'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; @@ -55,7 +55,7 @@ export function getServiceMetadataIcons({ const filter = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ]; const params = { diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts index 54cf89d6125b6e..ce36db3e82babe 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_comparison_statistics.ts @@ -14,7 +14,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../common/event_outcome'; import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { Coordinate } from '../../../typings/timeseries'; import { withApmSpan } from '../../utils/with_apm_span'; import { @@ -31,6 +31,7 @@ import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { calculateTransactionErrorPercentage } from '../helpers/transaction_error_rate'; export async function getServiceTransactionGroupComparisonStatistics({ + environment, serviceName, transactionNames, setup, @@ -39,6 +40,7 @@ export async function getServiceTransactionGroupComparisonStatistics({ transactionType, latencyAggregationType, }: { + environment?: string; serviceName: string; transactionNames: string[]; setup: Setup & SetupTimeRange; @@ -82,10 +84,11 @@ export async function getServiceTransactionGroupComparisonStatistics({ filter: [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts index 168eed8e38374c..ddbfd617faf65b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts @@ -13,7 +13,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../common/event_outcome'; import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { withApmSpan } from '../../utils/with_apm_span'; import { getDocumentTypeFilterForAggregatedTransactions, @@ -36,12 +36,14 @@ export type ServiceOverviewTransactionGroupSortField = | 'impact'; export async function getServiceTransactionGroups({ + environment, serviceName, setup, searchAggregatedTransactions, transactionType, latencyAggregationType, }: { + environment?: string; serviceName: string; setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; @@ -70,10 +72,11 @@ export async function getServiceTransactionGroups({ filter: [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts index bc4660e2c01a5e..3d77bf5bd6baf3 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts @@ -9,7 +9,7 @@ import { SERVICE_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getDocumentTypeFilterForAggregatedTransactions, @@ -46,7 +46,7 @@ export function getServiceTransactionTypes({ searchAggregatedTransactions ), { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts index e88fafb061912d..6fc868b0f0a4ec 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_health_statuses.ts @@ -8,28 +8,25 @@ import { getSeverity } from '../../../../common/anomaly_detection'; import { getServiceHealthStatus } from '../../../../common/service_health_status'; import { getServiceAnomalies } from '../../service_map/get_service_anomalies'; -import { - ServicesItemsProjection, - ServicesItemsSetup, -} from './get_services_items'; +import { ServicesItemsSetup } from './get_services_items'; interface AggregationParams { + environment?: string; setup: ServicesItemsSetup; - projection: ServicesItemsProjection; searchAggregatedTransactions: boolean; } -export const getHealthStatuses = async ( - { setup }: AggregationParams, - mlAnomaliesEnvironment?: string -) => { +export const getHealthStatuses = async ({ + environment, + setup, +}: AggregationParams) => { if (!setup.ml) { return []; } const anomalies = await getServiceAnomalies({ setup, - environment: mlAnomaliesEnvironment, + environment, }); return anomalies.serviceAnomalies.map((anomalyStats) => { diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts index acfdc1d8c1710a..e1f8bca83829cc 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts @@ -15,7 +15,7 @@ import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, } from '../../../../common/transaction_types'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; import { getDocumentTypeFilterForAggregatedTransactions, @@ -32,6 +32,7 @@ import { ServicesItemsSetup } from './get_services_items'; import { withApmSpan } from '../../../utils/with_apm_span'; interface AggregationParams { + environment?: string; setup: ServicesItemsSetup; searchAggregatedTransactions: boolean; } @@ -39,6 +40,7 @@ interface AggregationParams { const MAX_NUMBER_OF_SERVICES = 500; export async function getServiceTransactionStats({ + environment, setup, searchAggregatedTransactions, }: AggregationParams) { @@ -71,11 +73,12 @@ export async function getServiceTransactionStats({ query: { bool: { filter: [ - { range: rangeFilter(start, end) }, - ...esFilter, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ], }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts index 1ba9aaf980201f..c2677af038486b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts @@ -14,19 +14,21 @@ import { getHealthStatuses } from './get_health_statuses'; import { getServiceTransactionStats } from './get_service_transaction_stats'; export type ServicesItemsSetup = Setup & SetupTimeRange; -export type ServicesItemsProjection = ReturnType; export async function getServicesItems({ + environment, setup, searchAggregatedTransactions, logger, }: { + environment?: string; setup: ServicesItemsSetup; searchAggregatedTransactions: boolean; logger: Logger; }) { return withApmSpan('get_services_items', async () => { const params = { + environment, projection: getServicesProjection({ setup, searchAggregatedTransactions, @@ -37,7 +39,7 @@ export async function getServicesItems({ const [transactionStats, healthStatuses] = await Promise.all([ getServiceTransactionStats(params), - getHealthStatuses(params, setup.uiFilters.environment).catch((err) => { + getHealthStatuses(params).catch((err) => { logger.error(err); return []; }), diff --git a/x-pack/plugins/apm/server/lib/services/get_services/index.ts b/x-pack/plugins/apm/server/lib/services/get_services/index.ts index 45efd80c45a980..1a0ddeda11651b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/index.ts @@ -14,10 +14,12 @@ import { getServicesItems } from './get_services_items'; import { hasHistoricalAgentData } from './has_historical_agent_data'; export async function getServices({ + environment, setup, searchAggregatedTransactions, logger, }: { + environment?: string; setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; logger: Logger; @@ -25,6 +27,7 @@ export async function getServices({ return withApmSpan('get_services', async () => { const [items, hasLegacyData] = await Promise.all([ getServicesItems({ + environment, setup, searchAggregatedTransactions, logger, diff --git a/x-pack/plugins/apm/server/lib/services/get_throughput.ts b/x-pack/plugins/apm/server/lib/services/get_throughput.ts index 33268e9b3332d7..f7cd23b0e37a7b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_throughput.ts +++ b/x-pack/plugins/apm/server/lib/services/get_throughput.ts @@ -10,7 +10,7 @@ import { SERVICE_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -20,6 +20,7 @@ import { Setup } from '../helpers/setup_request'; import { withApmSpan } from '../../utils/with_apm_span'; interface Options { + environment?: string; searchAggregatedTransactions: boolean; serviceName: string; setup: Setup; @@ -29,6 +30,7 @@ interface Options { } function fetcher({ + environment, searchAggregatedTransactions, serviceName, setup, @@ -36,16 +38,17 @@ function fetcher({ start, end, }: Options) { - const { apmEventClient } = setup; + const { esFilter, apmEventClient } = setup; const { intervalString } = getBucketSize({ start, end }); const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), - ...setup.esFilter, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ]; const params = { diff --git a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts index bd3ecf1e0f862d..f631657f872761 100644 --- a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts +++ b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts @@ -15,7 +15,7 @@ import { ERROR_LOG_LEVEL, } from '../../../common/elasticsearch_fieldnames'; import { APMError } from '../../../typings/es_schemas/ui/apm_error'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { rangeQuery } from '../../../common/utils/queries'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { PromiseValueType } from '../../../typings/common'; import { withApmSpan } from '../../utils/with_apm_span'; @@ -44,7 +44,7 @@ export async function getTraceItems( bool: { filter: [ { term: { [TRACE_ID]: traceId } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], must_not: { terms: { [ERROR_LOG_LEVEL]: excludedLogLevels } }, }, @@ -74,7 +74,7 @@ export async function getTraceItems( bool: { filter: [ { term: { [TRACE_ID]: traceId } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], should: { exists: { field: PARENT_ID }, diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap index 443159611883fc..7fb2bb2fcbeeb1 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap @@ -244,12 +244,8 @@ Array [ "bool": Object { "filter": Array [ Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, + "term": Object { + "service.name": "foo", }, }, Object { @@ -258,8 +254,12 @@ Array [ }, }, Object { - "term": Object { - "service.name": "foo", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, }, }, Object { @@ -299,12 +299,8 @@ Array [ "bool": Object { "filter": Array [ Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, + "term": Object { + "service.name": "foo", }, }, Object { @@ -313,8 +309,12 @@ Array [ }, }, Object { - "term": Object { - "service.name": "foo", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, }, }, Object { @@ -354,12 +354,8 @@ Array [ "bool": Object { "filter": Array [ Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, + "term": Object { + "service.name": "foo", }, }, Object { @@ -368,8 +364,12 @@ Array [ }, }, Object { - "term": Object { - "service.name": "foo", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, }, }, Object { @@ -415,12 +415,8 @@ Array [ "bool": Object { "filter": Array [ Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, + "term": Object { + "service.name": "foo", }, }, Object { @@ -429,8 +425,12 @@ Array [ }, }, Object { - "term": Object { - "service.name": "foo", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, }, }, Object { diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts index 0fad948edde19b..09e5e358a1b7c1 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -27,6 +27,7 @@ import { } from './get_transaction_group_stats'; interface TopTransactionOptions { + environment?: string; type: 'top_transactions'; serviceName: string; transactionType: string; @@ -35,6 +36,7 @@ interface TopTransactionOptions { } interface TopTraceOptions { + environment?: string; type: 'top_traces'; transactionName?: string; searchAggregatedTransactions: boolean; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts index 839efc9009c389..d1a056002db078 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts @@ -14,7 +14,7 @@ import { TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../common/event_outcome'; -import { rangeFilter } from '../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../common/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -29,12 +29,14 @@ import { import { withApmSpan } from '../../utils/with_apm_span'; export async function getErrorRate({ + environment, serviceName, transactionType, transactionName, setup, searchAggregatedTransactions, }: { + environment?: string; serviceName: string; transactionType?: string; transactionName?: string; @@ -57,17 +59,18 @@ export async function getErrorRate({ const filter = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, { terms: { [EVENT_OUTCOME]: [EventOutcome.failure, EventOutcome.success], }, }, + ...transactionNamefilter, + ...transactionTypefilter, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), - ...transactionNamefilter, - ...transactionTypefilter, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ]; diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts index 8a2579b4a2b875..c3741184c807db 100644 --- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -18,18 +18,20 @@ import { TRANSACTION_BREAKDOWN_COUNT, } from '../../../../common/elasticsearch_fieldnames'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { getMetricsDateHistogramParams } from '../../helpers/metrics'; import { MAX_KPIS } from './constants'; import { getVizColorForIndex } from '../../../../common/viz_colors'; import { withApmSpan } from '../../../utils/with_apm_span'; export function getTransactionBreakdown({ + environment, setup, serviceName, transactionName, transactionType, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; transactionName?: string; @@ -82,7 +84,8 @@ export function getTransactionBreakdown({ const filters = [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ]; diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts index d1d23f538e96b0..7ed016cd4b4c61 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts @@ -17,7 +17,10 @@ import { } from '../../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../../common/processor_event'; import { joinByKey } from '../../../../../common/utils/join_by_key'; -import { rangeFilter } from '../../../../../common/utils/range_filter'; +import { + environmentQuery, + rangeQuery, +} from '../../../../../common/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -46,6 +49,7 @@ function getHistogramAggOptions({ } export async function getBuckets({ + environment, serviceName, transactionName, transactionType, @@ -56,6 +60,7 @@ export async function getBuckets({ setup, searchAggregatedTransactions, }: { + environment?: string; serviceName: string; transactionName: string; transactionType: string; @@ -75,7 +80,8 @@ export async function getBuckets({ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, { term: { [TRANSACTION_NAME]: transactionName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ]; diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts index ed54dae32704e3..f8061ea9894698 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts @@ -15,15 +15,18 @@ import { getProcessorEventForAggregatedTransactions, getTransactionDurationFieldForAggregatedTransactions, } from '../../helpers/aggregated_transactions'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { withApmSpan } from '../../../utils/with_apm_span'; export async function getDistributionMax({ + environment, serviceName, transactionName, transactionType, setup, searchAggregatedTransactions, }: { + environment?: string; serviceName: string; transactionName: string; transactionType: string; @@ -49,15 +52,8 @@ export async function getDistributionMax({ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, { term: { [TRANSACTION_NAME]: transactionName } }, - { - range: { - '@timestamp': { - gte: start, - lte: end, - format: 'epoch_millis', - }, - }, - }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts index 22436cac40183d..92d1d96b4a8e3a 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts @@ -20,6 +20,7 @@ function getBucketSize(max: number) { } export async function getTransactionDistribution({ + environment, serviceName, transactionName, transactionType, @@ -28,6 +29,7 @@ export async function getTransactionDistribution({ setup, searchAggregatedTransactions, }: { + environment?: string; serviceName: string; transactionName: string; transactionType: string; @@ -38,6 +40,7 @@ export async function getTransactionDistribution({ }) { return withApmSpan('get_transaction_latency_distribution', async () => { const distributionMax = await getDistributionMax({ + environment, serviceName, transactionName, transactionType, @@ -52,6 +55,7 @@ export async function getTransactionDistribution({ const bucketSize = getBucketSize(distributionMax); const { buckets, noHits } = await getBuckets({ + environment, serviceName, transactionName, transactionType, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts index 002ddd1ec35f04..d566f3a169e78a 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts @@ -7,6 +7,7 @@ import { ESSearchResponse } from '../../../../../../typings/elasticsearch'; import { PromiseReturnType } from '../../../../../observability/typings/common'; +import { rangeQuery } from '../../../../common/utils/queries'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup } from '../../helpers/setup_request'; @@ -40,15 +41,7 @@ export function anomalySeriesFetcher({ { terms: { result_type: ['model_plot', 'record'] } }, { term: { partition_field_value: serviceName } }, { term: { by_field_value: transactionType } }, - { - range: { - timestamp: { - gte: start, - lte: end, - format: 'epoch_millis', - }, - }, - }, + ...rangeQuery(start, end, 'timestamp'), ], }, }, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts index 29dd562330cc15..a03b1ac82e90a6 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts @@ -18,20 +18,21 @@ import { ANOMALY_THRESHOLD } from '../../../../../ml/common'; import { withApmSpan } from '../../../utils/with_apm_span'; export async function getAnomalySeries({ + environment, serviceName, transactionType, transactionName, setup, logger, }: { + environment?: string; serviceName: string; transactionType: string; transactionName?: string; setup: Setup & SetupTimeRange; logger: Logger; }) { - const { uiFilters, start, end, ml } = setup; - const { environment } = uiFilters; + const { start, end, ml } = setup; // don't fetch anomalies if the ML plugin is not setup if (!ml) { @@ -45,18 +46,17 @@ export async function getAnomalySeries({ } // don't fetch anomalies when no specific environment is selected - if (environment === ENVIRONMENT_ALL.value) { + if (!environment || environment === ENVIRONMENT_ALL.value) { return undefined; } - // don't fetch anomalies if unknown uiFilters are applied - const knownFilters = ['environment', 'serviceName']; - const hasUnknownFiltersApplied = Object.entries(setup.uiFilters) - .filter(([key, value]) => !!value) - .map(([key]) => key) - .some((uiFilterName) => !knownFilters.includes(uiFilterName)); + // Don't fetch anomalies if uiFilters are applied. This filters out anything + // with empty values so `kuery: ''` returns false but `kuery: 'x:y'` returns true. + const hasUiFiltersApplied = + Object.entries(setup.uiFilters).filter(([_key, value]) => !!value).length > + 0; - if (hasUnknownFiltersApplied) { + if (hasUiFiltersApplied) { return undefined; } diff --git a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts index ee27d00fdc0d4d..e1d3921d298c77 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts @@ -13,7 +13,7 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -31,6 +31,7 @@ export type LatencyChartsSearchResponse = PromiseReturnType< >; function searchLatency({ + environment, serviceName, transactionType, transactionName, @@ -38,6 +39,7 @@ function searchLatency({ searchAggregatedTransactions, latencyAggregationType, }: { + environment?: string; serviceName: string; transactionType: string | undefined; transactionName: string | undefined; @@ -45,16 +47,17 @@ function searchLatency({ searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; }) { - const { start, end, apmEventClient } = setup; + const { esFilter, start, end, apmEventClient } = setup; const { intervalString } = getBucketSize({ start, end }); const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), - ...setup.esFilter, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ]; if (transactionName) { @@ -102,6 +105,7 @@ function searchLatency({ } export function getLatencyTimeseries({ + environment, serviceName, transactionType, transactionName, @@ -109,6 +113,7 @@ export function getLatencyTimeseries({ searchAggregatedTransactions, latencyAggregationType, }: { + environment?: string; serviceName: string; transactionType: string | undefined; transactionName: string | undefined; @@ -118,6 +123,7 @@ export function getLatencyTimeseries({ }) { return withApmSpan('get_latency_charts', async () => { const response = await searchLatency({ + environment, serviceName, transactionType, transactionName, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts index 7c1fa9c3a2368e..ec5dbf0eab3e9e 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts @@ -13,7 +13,7 @@ import { TRANSACTION_RESULT, TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -28,6 +28,7 @@ export type ThroughputChartsResponse = PromiseReturnType< >; function searchThroughput({ + environment, serviceName, transactionType, transactionName, @@ -35,6 +36,7 @@ function searchThroughput({ searchAggregatedTransactions, intervalString, }: { + environment?: string; serviceName: string; transactionType: string; transactionName: string | undefined; @@ -42,16 +44,17 @@ function searchThroughput({ searchAggregatedTransactions: boolean; intervalString: string; }) { - const { start, end, apmEventClient } = setup; + const { esFilter, start, end, apmEventClient } = setup; const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + { term: { [TRANSACTION_TYPE]: transactionType } }, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), - { term: { [TRANSACTION_TYPE]: transactionType } }, - ...setup.esFilter, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ]; if (transactionName) { @@ -91,12 +94,14 @@ function searchThroughput({ } export async function getThroughputCharts({ + environment, serviceName, transactionType, transactionName, setup, searchAggregatedTransactions, }: { + environment?: string; serviceName: string; transactionType: string; transactionName: string | undefined; @@ -107,6 +112,7 @@ export async function getThroughputCharts({ const { bucketSize, intervalString } = getBucketSize(setup); const response = await searchThroughput({ + environment, serviceName, transactionType, transactionName, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts index 2fdb8e25fd9962..38d6b593dc72dc 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts @@ -9,7 +9,7 @@ import { TRACE_ID, TRANSACTION_ID, } from '../../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../../common/utils/range_filter'; +import { rangeQuery } from '../../../../common/utils/queries'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { ProcessorEvent } from '../../../../common/processor_event'; import { withApmSpan } from '../../../utils/with_apm_span'; @@ -37,7 +37,7 @@ export function getTransaction({ filter: [ { term: { [TRANSACTION_ID]: transactionId } }, { term: { [TRACE_ID]: traceId } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), ], }, }, diff --git a/x-pack/plugins/apm/server/projections/errors.ts b/x-pack/plugins/apm/server/projections/errors.ts index 082fd53a0ca93d..342d78608efbf6 100644 --- a/x-pack/plugins/apm/server/projections/errors.ts +++ b/x-pack/plugins/apm/server/projections/errors.ts @@ -10,13 +10,15 @@ import { SERVICE_NAME, ERROR_GROUP_ID, } from '../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../common/utils/queries'; import { ProcessorEvent } from '../../common/processor_event'; export function getErrorGroupsProjection({ + environment, setup, serviceName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; }) { @@ -31,7 +33,8 @@ export function getErrorGroupsProjection({ bool: { filter: [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ], }, diff --git a/x-pack/plugins/apm/server/projections/metrics.ts b/x-pack/plugins/apm/server/projections/metrics.ts index f6c3f85ed48075..a32c2ae46c870b 100644 --- a/x-pack/plugins/apm/server/projections/metrics.ts +++ b/x-pack/plugins/apm/server/projections/metrics.ts @@ -10,7 +10,7 @@ import { SERVICE_NAME, SERVICE_NODE_NAME, } from '../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../common/utils/queries'; import { SERVICE_NODE_NAME_MISSING } from '../../common/service_nodes'; import { ProcessorEvent } from '../../common/processor_event'; @@ -27,10 +27,12 @@ function getServiceNodeNameFilters(serviceNodeName?: string) { } export function getMetricsProjection({ + environment, setup, serviceName, serviceNodeName, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName: string; serviceNodeName?: string; @@ -39,8 +41,9 @@ export function getMetricsProjection({ const filter = [ { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, ...getServiceNodeNameFilters(serviceNodeName), + ...rangeQuery(start, end), + ...environmentQuery(environment), ...esFilter, ]; diff --git a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts index ff8d868bc4abe5..1d5f7316b69ad6 100644 --- a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts +++ b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @@ -11,7 +11,7 @@ import { TRANSACTION_TYPE, SERVICE_LANGUAGE_NAME, } from '../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../common/utils/range_filter'; +import { rangeQuery } from '../../common/utils/queries'; import { ProcessorEvent } from '../../common/processor_event'; import { TRANSACTION_PAGE_LOAD } from '../../common/transaction_types'; @@ -28,7 +28,7 @@ export function getRumPageLoadTransactionsProjection({ const bool = { filter: [ - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), { term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }, ...(checkFetchStartFieldExists ? [ @@ -79,7 +79,7 @@ export function getRumErrorsProjection({ const bool = { filter: [ - { range: rangeFilter(start, end) }, + ...rangeQuery(start, end), { term: { [AGENT_NAME]: 'rum-js' } }, { term: { diff --git a/x-pack/plugins/apm/server/projections/services.ts b/x-pack/plugins/apm/server/projections/services.ts index 33ffb45a006378..a9f5a7efd0e67e 100644 --- a/x-pack/plugins/apm/server/projections/services.ts +++ b/x-pack/plugins/apm/server/projections/services.ts @@ -7,7 +7,7 @@ import { Setup, SetupTimeRange } from '../../server/lib/helpers/setup_request'; import { SERVICE_NAME } from '../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../common/utils/range_filter'; +import { rangeQuery } from '../../common/utils/queries'; import { ProcessorEvent } from '../../common/processor_event'; import { getProcessorEventForAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; @@ -34,7 +34,7 @@ export function getServicesProjection({ size: 0, query: { bool: { - filter: [{ range: rangeFilter(start, end) }, ...esFilter], + filter: [...rangeQuery(start, end), ...esFilter], }, }, aggs: { diff --git a/x-pack/plugins/apm/server/projections/transactions.ts b/x-pack/plugins/apm/server/projections/transactions.ts index 76f2fc164e3fd2..45ed5d2865a67e 100644 --- a/x-pack/plugins/apm/server/projections/transactions.ts +++ b/x-pack/plugins/apm/server/projections/transactions.ts @@ -11,19 +11,21 @@ import { TRANSACTION_TYPE, TRANSACTION_NAME, } from '../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../common/utils/range_filter'; +import { environmentQuery, rangeQuery } from '../../common/utils/queries'; import { getProcessorEventForAggregatedTransactions, getDocumentTypeFilterForAggregatedTransactions, } from '../lib/helpers/aggregated_transactions'; export function getTransactionsProjection({ + environment, setup, serviceName, transactionName, transactionType, searchAggregatedTransactions, }: { + environment?: string; setup: Setup & SetupTimeRange; serviceName?: string; transactionName?: string; @@ -44,14 +46,15 @@ export function getTransactionsProjection({ const bool = { filter: [ - { range: rangeFilter(start, end) }, + ...serviceNameFilter, ...transactionNameFilter, ...transactionTypeFilter, - ...serviceNameFilter, - ...esFilter, ...getDocumentTypeFilterForAggregatedTransactions( searchAggregatedTransactions ), + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, ], }; diff --git a/x-pack/plugins/apm/server/routes/correlations.ts b/x-pack/plugins/apm/server/routes/correlations.ts index 3c2ff00153ce79..d4a0db3c0d6c7b 100644 --- a/x-pack/plugins/apm/server/routes/correlations.ts +++ b/x-pack/plugins/apm/server/routes/correlations.ts @@ -13,7 +13,7 @@ import { getCorrelationsForFailedTransactions } from '../lib/correlations/get_co import { getCorrelationsForSlowTransactions } from '../lib/correlations/get_correlations_for_slow_transactions'; import { setupRequest } from '../lib/helpers/setup_request'; import { createRoute } from './create_route'; -import { rangeRt } from './default_api_types'; +import { environmentRt, rangeRt } from './default_api_types'; const INVALID_LICENSE = i18n.translate( 'xpack.apm.significanTerms.license.text', @@ -37,6 +37,7 @@ export const correlationsForSlowTransactionsRoute = createRoute({ fieldNames: t.string, }), t.partial({ uiFilters: t.string }), + environmentRt, rangeRt, ]), }), @@ -47,6 +48,7 @@ export const correlationsForSlowTransactionsRoute = createRoute({ } const setup = await setupRequest(context, request); const { + environment, serviceName, transactionType, transactionName, @@ -55,6 +57,7 @@ export const correlationsForSlowTransactionsRoute = createRoute({ } = context.params.query; return getCorrelationsForSlowTransactions({ + environment, serviceName, transactionType, transactionName, @@ -78,6 +81,7 @@ export const correlationsForFailedTransactionsRoute = createRoute({ fieldNames: t.string, }), t.partial({ uiFilters: t.string }), + environmentRt, rangeRt, ]), }), @@ -88,14 +92,15 @@ export const correlationsForFailedTransactionsRoute = createRoute({ } const setup = await setupRequest(context, request); const { + environment, serviceName, transactionType, transactionName, - fieldNames, } = context.params.query; return getCorrelationsForFailedTransactions({ + environment, serviceName, transactionType, transactionName, diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index fc5d6a3dd0bcdb..822a45fca269fd 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -63,8 +63,8 @@ import { transactionChartsErrorRateRoute, transactionGroupsRoute, transactionGroupsPrimaryStatisticsRoute, - transactionLatencyChatsRoute, - transactionThroughputChatsRoute, + transactionLatencyChartsRoute, + transactionThroughputChartsRoute, transactionGroupsComparisonStatisticsRoute, } from './transactions'; import { serviceMapRoute, serviceMapServiceNodeRoute } from './service_map'; @@ -167,8 +167,8 @@ const createApmApi = () => { .add(transactionChartsErrorRateRoute) .add(transactionGroupsRoute) .add(transactionGroupsPrimaryStatisticsRoute) - .add(transactionLatencyChatsRoute) - .add(transactionThroughputChatsRoute) + .add(transactionLatencyChartsRoute) + .add(transactionThroughputChartsRoute) .add(transactionGroupsComparisonStatisticsRoute) // Service map diff --git a/x-pack/plugins/apm/server/routes/default_api_types.ts b/x-pack/plugins/apm/server/routes/default_api_types.ts index fdc1e8ebe5a55f..990b462a520d23 100644 --- a/x-pack/plugins/apm/server/routes/default_api_types.ts +++ b/x-pack/plugins/apm/server/routes/default_api_types.ts @@ -18,4 +18,6 @@ export const comparisonRangeRt = t.partial({ comparisonEnd: isoToEpochRt, }); +export const environmentRt = t.partial({ environment: t.string }); + export const uiFiltersRt = t.type({ uiFilters: t.string }); diff --git a/x-pack/plugins/apm/server/routes/errors.ts b/x-pack/plugins/apm/server/routes/errors.ts index cc9db2e6a48551..073a91bfe15487 100644 --- a/x-pack/plugins/apm/server/routes/errors.ts +++ b/x-pack/plugins/apm/server/routes/errors.ts @@ -11,7 +11,7 @@ import { getErrorDistribution } from '../lib/errors/distribution/get_distributio import { getErrorGroupSample } from '../lib/errors/get_error_group_sample'; import { getErrorGroups } from '../lib/errors/get_error_groups'; import { setupRequest } from '../lib/helpers/setup_request'; -import { uiFiltersRt, rangeRt } from './default_api_types'; +import { environmentRt, uiFiltersRt, rangeRt } from './default_api_types'; export const errorsRoute = createRoute({ endpoint: 'GET /api/apm/services/{serviceName}/errors', @@ -24,6 +24,7 @@ export const errorsRoute = createRoute({ sortField: t.string, sortDirection: t.union([t.literal('asc'), t.literal('desc')]), }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -33,9 +34,10 @@ export const errorsRoute = createRoute({ const setup = await setupRequest(context, request); const { params } = context; const { serviceName } = params.path; - const { sortField, sortDirection } = params.query; + const { environment, sortField, sortDirection } = params.query; return getErrorGroups({ + environment, serviceName, sortField, sortDirection, @@ -51,13 +53,15 @@ export const errorGroupsRoute = createRoute({ serviceName: t.string, groupId: t.string, }), - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([environmentRt, uiFiltersRt, rangeRt]), }), options: { tags: ['access:apm'] }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName, groupId } = context.params.path; - return getErrorGroupSample({ serviceName, groupId, setup }); + const { environment } = context.params.query; + + return getErrorGroupSample({ environment, serviceName, groupId, setup }); }, }); @@ -71,6 +75,7 @@ export const errorDistributionRoute = createRoute({ t.partial({ groupId: t.string, }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -80,7 +85,7 @@ export const errorDistributionRoute = createRoute({ const setup = await setupRequest(context, request); const { params } = context; const { serviceName } = params.path; - const { groupId } = params.query; - return getErrorDistribution({ serviceName, groupId, setup }); + const { environment, groupId } = params.query; + return getErrorDistribution({ environment, serviceName, groupId, setup }); }, }); diff --git a/x-pack/plugins/apm/server/routes/metrics.ts b/x-pack/plugins/apm/server/routes/metrics.ts index d07504a1046ee5..08376ed0e37ffb 100644 --- a/x-pack/plugins/apm/server/routes/metrics.ts +++ b/x-pack/plugins/apm/server/routes/metrics.ts @@ -9,7 +9,7 @@ import * as t from 'io-ts'; import { setupRequest } from '../lib/helpers/setup_request'; import { getMetricsChartDataByAgent } from '../lib/metrics/get_metrics_chart_data_by_agent'; import { createRoute } from './create_route'; -import { uiFiltersRt, rangeRt } from './default_api_types'; +import { environmentRt, uiFiltersRt, rangeRt } from './default_api_types'; export const metricsChartsRoute = createRoute({ endpoint: `GET /api/apm/services/{serviceName}/metrics/charts`, @@ -24,6 +24,7 @@ export const metricsChartsRoute = createRoute({ t.partial({ serviceNodeName: t.string, }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -33,8 +34,9 @@ export const metricsChartsRoute = createRoute({ const setup = await setupRequest(context, request); const { params } = context; const { serviceName } = params.path; - const { agentName, serviceNodeName } = params.query; + const { agentName, environment, serviceNodeName } = params.query; return await getMetricsChartDataByAgent({ + environment, setup, serviceName, agentName, diff --git a/x-pack/plugins/apm/server/routes/service_map.ts b/x-pack/plugins/apm/server/routes/service_map.ts index 7cca6cd0a19430..65c7b245958f32 100644 --- a/x-pack/plugins/apm/server/routes/service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map.ts @@ -12,7 +12,7 @@ import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceMap } from '../lib/service_map/get_service_map'; import { getServiceMapServiceNodeInfo } from '../lib/service_map/get_service_map_service_node_info'; import { createRoute } from './create_route'; -import { rangeRt, uiFiltersRt } from './default_api_types'; +import { environmentRt, rangeRt, uiFiltersRt } from './default_api_types'; import { notifyFeatureUsage } from '../feature'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { isActivePlatinumLicense } from '../../common/license_check'; @@ -22,9 +22,9 @@ export const serviceMapRoute = createRoute({ params: t.type({ query: t.intersection([ t.partial({ - environment: t.string, serviceName: t.string, }), + environmentRt, rangeRt, ]), }), diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index 84ccb4b06c2e6a..e59b438305b349 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -25,23 +25,29 @@ import { getServiceTransactionTypes } from '../lib/services/get_service_transact import { getThroughput } from '../lib/services/get_throughput'; import { offsetPreviousPeriodCoordinates } from '../utils/offset_previous_period_coordinate'; import { createRoute } from './create_route'; -import { comparisonRangeRt, rangeRt, uiFiltersRt } from './default_api_types'; +import { + comparisonRangeRt, + environmentRt, + rangeRt, + uiFiltersRt, +} from './default_api_types'; import { withApmSpan } from '../utils/with_apm_span'; export const servicesRoute = createRoute({ endpoint: 'GET /api/apm/services', params: t.type({ - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([environmentRt, uiFiltersRt, rangeRt]), }), options: { tags: ['access:apm'] }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - + const { environment } = context.params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); const services = await getServices({ + environment, setup, searchAggregatedTransactions, logger: context.logger, @@ -273,6 +279,7 @@ export const serviceErrorGroupsRoute = createRoute({ serviceName: t.string, }), query: t.intersection([ + environmentRt, rangeRt, uiFiltersRt, t.type({ @@ -296,6 +303,7 @@ export const serviceErrorGroupsRoute = createRoute({ const { path: { serviceName }, query: { + environment, numBuckets, pageIndex, size, @@ -306,6 +314,7 @@ export const serviceErrorGroupsRoute = createRoute({ } = context.params; return getServiceErrorGroups({ + environment, serviceName, setup, size, @@ -326,6 +335,7 @@ export const serviceThroughputRoute = createRoute({ }), query: t.intersection([ t.type({ transactionType: t.string }), + environmentRt, uiFiltersRt, rangeRt, comparisonRangeRt, @@ -336,6 +346,7 @@ export const serviceThroughputRoute = createRoute({ const setup = await setupRequest(context, request); const { serviceName } = context.params.path; const { + environment, transactionType, comparisonStart, comparisonEnd, @@ -356,12 +367,14 @@ export const serviceThroughputRoute = createRoute({ const [currentPeriod, previousPeriod] = await Promise.all([ getThroughput({ ...commonProps, + environment, start, end, }), comparisonStart && comparisonEnd ? getThroughput({ ...commonProps, + environment, start: comparisonStart, end: comparisonEnd, }).then((coordinates) => @@ -389,6 +402,7 @@ export const serviceInstancesRoute = createRoute({ }), query: t.intersection([ t.type({ transactionType: t.string, numBuckets: toNumberRt }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -397,13 +411,14 @@ export const serviceInstancesRoute = createRoute({ handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; - const { transactionType, numBuckets } = context.params.query; + const { environment, transactionType, numBuckets } = context.params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); return getServiceInstances({ + environment, serviceName, setup, transactionType, @@ -421,9 +436,9 @@ export const serviceDependenciesRoute = createRoute({ }), query: t.intersection([ t.type({ - environment: t.string, numBuckets: toNumberRt, }), + environmentRt, rangeRt, ]), }), diff --git a/x-pack/plugins/apm/server/routes/traces.ts b/x-pack/plugins/apm/server/routes/traces.ts index 722675906487ce..5d3f99be7af344 100644 --- a/x-pack/plugins/apm/server/routes/traces.ts +++ b/x-pack/plugins/apm/server/routes/traces.ts @@ -10,23 +10,25 @@ import { setupRequest } from '../lib/helpers/setup_request'; import { getTrace } from '../lib/traces/get_trace'; import { getTransactionGroupList } from '../lib/transaction_groups'; import { createRoute } from './create_route'; -import { rangeRt, uiFiltersRt } from './default_api_types'; +import { environmentRt, rangeRt, uiFiltersRt } from './default_api_types'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { getRootTransactionByTraceId } from '../lib/transactions/get_transaction_by_trace'; export const tracesRoute = createRoute({ endpoint: 'GET /api/apm/traces', params: t.type({ - query: t.intersection([rangeRt, uiFiltersRt]), + query: t.intersection([environmentRt, rangeRt, uiFiltersRt]), }), options: { tags: ['access:apm'] }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); + const { environment } = context.params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); + return getTransactionGroupList( - { type: 'top_traces', searchAggregatedTransactions }, + { environment, type: 'top_traces', searchAggregatedTransactions }, setup ); }, diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index bef96cb7f0767e..5a4be216a817c2 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -5,7 +5,6 @@ * 2.0. */ -import Boom from '@hapi/boom'; import * as t from 'io-ts'; import { LatencyAggregationType, @@ -25,7 +24,7 @@ import { getThroughputCharts } from '../lib/transactions/get_throughput_charts'; import { getTransactionGroupList } from '../lib/transaction_groups'; import { getErrorRate } from '../lib/transaction_groups/get_error_rate'; import { createRoute } from './create_route'; -import { rangeRt, uiFiltersRt } from './default_api_types'; +import { environmentRt, rangeRt, uiFiltersRt } from './default_api_types'; /** * Returns a list of transactions grouped by name @@ -39,6 +38,7 @@ export const transactionGroupsRoute = createRoute({ }), query: t.intersection([ t.type({ transactionType: t.string }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -47,7 +47,7 @@ export const transactionGroupsRoute = createRoute({ handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; - const { transactionType } = context.params.query; + const { environment, transactionType } = context.params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup @@ -55,6 +55,7 @@ export const transactionGroupsRoute = createRoute({ return getTransactionGroupList( { + environment, type: 'top_transactions', serviceName, transactionType, @@ -71,6 +72,7 @@ export const transactionGroupsPrimaryStatisticsRoute = createRoute({ params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ + environmentRt, rangeRt, uiFiltersRt, t.type({ @@ -91,10 +93,11 @@ export const transactionGroupsPrimaryStatisticsRoute = createRoute({ const { path: { serviceName }, - query: { latencyAggregationType, transactionType }, + query: { environment, latencyAggregationType, transactionType }, } = context.params; return getServiceTransactionGroups({ + environment, setup, serviceName, searchAggregatedTransactions, @@ -110,6 +113,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({ params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ + environmentRt, rangeRt, uiFiltersRt, t.type({ @@ -133,6 +137,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({ const { path: { serviceName }, query: { + environment, transactionNames, latencyAggregationType, numBuckets, @@ -141,6 +146,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({ } = context.params; return getServiceTransactionGroupComparisonStatistics({ + environment, setup, serviceName, transactionNames, @@ -152,7 +158,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({ }, }); -export const transactionLatencyChatsRoute = createRoute({ +export const transactionLatencyChartsRoute = createRoute({ endpoint: 'GET /api/apm/services/{serviceName}/transactions/charts/latency', params: t.type({ path: t.type({ @@ -166,6 +172,7 @@ export const transactionLatencyChatsRoute = createRoute({ transactionType: t.string, latencyAggregationType: latencyAggregationTypeRt, }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -176,22 +183,18 @@ export const transactionLatencyChatsRoute = createRoute({ const logger = context.logger; const { serviceName } = context.params.path; const { + environment, transactionType, transactionName, latencyAggregationType, } = context.params.query; - if (!setup.uiFilters.environment) { - throw Boom.badRequest( - `environment is a required property of the ?uiFilters JSON for transaction_groups/charts.` - ); - } - const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); const options = { + environment, serviceName, transactionType, transactionName, @@ -222,7 +225,7 @@ export const transactionLatencyChatsRoute = createRoute({ }, }); -export const transactionThroughputChatsRoute = createRoute({ +export const transactionThroughputChartsRoute = createRoute({ endpoint: 'GET /api/apm/services/{serviceName}/transactions/charts/throughput', params: t.type({ @@ -234,25 +237,25 @@ export const transactionThroughputChatsRoute = createRoute({ t.partial({ transactionName: t.string }), uiFiltersRt, rangeRt, + environmentRt, ]), }), options: { tags: ['access:apm'] }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; - const { transactionType, transactionName } = context.params.query; - - if (!setup.uiFilters.environment) { - throw Boom.badRequest( - `environment is a required property of the ?uiFilters JSON for transaction_groups/charts.` - ); - } + const { + environment, + transactionType, + transactionName, + } = context.params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); return await getThroughputCharts({ + environment, serviceName, transactionType, transactionName, @@ -278,6 +281,7 @@ export const transactionChartsDistributionRoute = createRoute({ transactionId: t.string, traceId: t.string, }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -287,6 +291,7 @@ export const transactionChartsDistributionRoute = createRoute({ const setup = await setupRequest(context, request); const { serviceName } = context.params.path; const { + environment, transactionType, transactionName, transactionId = '', @@ -298,6 +303,7 @@ export const transactionChartsDistributionRoute = createRoute({ ); return getTransactionDistribution({ + environment, serviceName, transactionType, transactionName, @@ -318,6 +324,7 @@ export const transactionChartsBreakdownRoute = createRoute({ query: t.intersection([ t.type({ transactionType: t.string }), t.partial({ transactionName: t.string }), + environmentRt, uiFiltersRt, rangeRt, ]), @@ -326,9 +333,14 @@ export const transactionChartsBreakdownRoute = createRoute({ handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; - const { transactionName, transactionType } = context.params.query; + const { + environment, + transactionName, + transactionType, + } = context.params.query; return getTransactionBreakdown({ + environment, serviceName, transactionName, transactionType, @@ -345,6 +357,7 @@ export const transactionChartsErrorRateRoute = createRoute({ serviceName: t.string, }), query: t.intersection([ + environmentRt, uiFiltersRt, rangeRt, t.type({ transactionType: t.string }), @@ -356,13 +369,14 @@ export const transactionChartsErrorRateRoute = createRoute({ const setup = await setupRequest(context, request); const { params } = context; const { serviceName } = params.path; - const { transactionType, transactionName } = params.query; + const { environment, transactionType, transactionName } = params.query; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); return getErrorRate({ + environment, serviceName, transactionType, transactionName, diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx index 19ac562121d9dc..4df638cc2c5df2 100644 --- a/x-pack/plugins/apm/server/utils/test_helpers.tsx +++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx @@ -88,7 +88,7 @@ export async function inspectSearchParams( }, } ) as APMConfig, - uiFilters: { environment: 'test' }, + uiFilters: {}, esFilter: [{ term: { 'service.environment': 'test' } }], indices: { /* eslint-disable @typescript-eslint/naming-convention */ diff --git a/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts index 1d00d54a3997bf..4474d0996175bb 100644 --- a/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts +++ b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts @@ -31,7 +31,7 @@ export default function rumHasDataApiTests({ getService }: FtrProviderContext) { 'has RUM data with data', { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, () => { - it('returns that it has data and service name with most traffice', async () => { + it('returns that it has data and service name with most traffic', async () => { const response = await supertest.get( '/api/apm/observability_overview/has_rum_data?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=' ); diff --git a/x-pack/test/apm_api_integration/tests/feature_controls.ts b/x-pack/test/apm_api_integration/tests/feature_controls.ts index db2b42323c4f64..45114dd506716d 100644 --- a/x-pack/test/apm_api_integration/tests/feature_controls.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.ts @@ -109,28 +109,28 @@ export default function featureControlsTests({ getService }: FtrProviderContext) }, { req: { - url: `/api/apm/services/foo/transactions/charts/latency?start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&uiFilters=%7B%22environment%22%3A%22testing%22%7D`, + url: `/api/apm/services/foo/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&uiFilters=%7B%7D`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/transactions/charts/latency?start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&transactionName=baz&uiFilters=%7B%22environment%22%3A%22testing%22%7D`, + url: `/api/apm/services/foo/transactions/charts/latency?environment=testing&start=${start}&end=${end}&transactionType=bar&latencyAggregationType=avg&transactionName=baz&uiFilters=%7B%7D`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/transactions/charts/throughput?start=${start}&end=${end}&transactionType=bar&uiFilters=%7B%22environment%22%3A%22testing%22%7D`, + url: `/api/apm/services/foo/transactions/charts/throughput?environment=testing&start=${start}&end=${end}&transactionType=bar&uiFilters=%7B%7D`, }, expectForbidden: expect403, expectResponse: expect200, }, { req: { - url: `/api/apm/services/foo/transactions/charts/throughput?start=${start}&end=${end}&transactionType=bar&transactionName=baz&uiFilters=%7B%22environment%22%3A%22testing%22%7D`, + url: `/api/apm/services/foo/transactions/charts/throughput?environment=testing&start=${start}&end=${end}&transactionType=bar&transactionName=baz&uiFilters=%7B%7D`, }, expectForbidden: expect403, expectResponse: expect200, diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.ts b/x-pack/test/apm_api_integration/tests/services/top_services.ts index efd0b410cf8aaa..3afaec653fcb35 100644 --- a/x-pack/test/apm_api_integration/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/tests/services/top_services.ts @@ -346,8 +346,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { let response: PromiseReturnType; before(async () => { response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${encodeURIComponent( - `{"kuery":"service.name:opbeans-java","environment":"ENVIRONMENT_ALL"}` + `/api/apm/services?environment=ENVIRONMENT_ALL&start=${start}&end=${end}&uiFilters=${encodeURIComponent( + `{"kuery":"service.name:opbeans-java"}` )}` ); }); diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap index a384ca2c9364e8..11c557fd02e381 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded when not defined environments seleted should return the correct anomaly boundaries 1`] = ` +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded when not defined environment is selected should return the correct anomaly boundaries 1`] = ` Array [ Object { "x": 1607436000000, @@ -30,7 +30,7 @@ Array [ ] `; -exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected in uiFilters should return a non-empty anomaly series 1`] = ` +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected should return a non-empty anomaly series 1`] = ` Array [ Object { "x": 1607436000000, diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/tests/transactions/latency.ts index 3003c6f902f39e..523139717b309e 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/latency.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/latency.ts @@ -26,10 +26,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'Latency with a basic license when data is not loaded ', { config: 'basic', archives: [] }, () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); + const uiFilters = encodeURIComponent(JSON.stringify({})); it('returns 400 when latencyAggregationType is not informed', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request` ); expect(response.status).to.be(400); @@ -37,7 +37,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns 400 when transactionType is not informed', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); expect(response.status).to.be(400); @@ -45,7 +45,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('handles the empty state', async () => { const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg&transactionType=request` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg&transactionType=request` ); expect(response.status).to.be(200); @@ -62,12 +62,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { () => { let response: PromiseReturnType; - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); + const uiFilters = encodeURIComponent(JSON.stringify({})); describe('average latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=avg` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=avg` ); }); @@ -81,7 +81,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('95th percentile latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p95` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p95` ); }); @@ -95,7 +95,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('99th percentile latency type', () => { before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p99` + `/api/apm/services/opbeans-node/transactions/charts/latency?environment=testing&start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p99` ); }); @@ -116,15 +116,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { const transactionType = 'request'; - describe('without environment', () => { + describe('without an environment', () => { const uiFilters = encodeURIComponent(JSON.stringify({})); before(async () => { response = await supertest.get( `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); }); - it('should return an error response', () => { - expect(response.status).to.eql(400); + + it('returns an ok response', () => { + expect(response.status).to.eql(200); }); }); @@ -139,11 +140,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - describe('with environment selected in uiFilters', () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'production' })); + describe('with environment selected', () => { + const uiFilters = encodeURIComponent(JSON.stringify({})); before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + `/api/apm/services/opbeans-java/transactions/charts/latency?environment=production&start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); }); @@ -166,13 +167,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - describe('when not defined environments seleted', () => { - const uiFilters = encodeURIComponent( - JSON.stringify({ environment: 'ENVIRONMENT_NOT_DEFINED' }) - ); + describe('when not defined environment is selected', () => { + const uiFilters = encodeURIComponent(JSON.stringify({})); before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-python/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + `/api/apm/services/opbeans-python/transactions/charts/latency?environment=ENVIRONMENT_NOT_DEFINED&start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); }); @@ -195,10 +194,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('with all environments selected', () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'ENVIRONMENT_ALL' })); + const uiFilters = encodeURIComponent(JSON.stringify({})); before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + `/api/apm/services/opbeans-java/transactions/charts/latency?environment=ENVIRONMENT_ALL&start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); }); @@ -212,12 +211,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('with environment selected and empty kuery filter', () => { - const uiFilters = encodeURIComponent( - JSON.stringify({ kuery: '', environment: 'production' }) - ); + const uiFilters = encodeURIComponent(JSON.stringify({ kuery: '' })); before(async () => { response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + `/api/apm/services/opbeans-java/transactions/charts/latency?environment=production&start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` ); }); diff --git a/x-pack/test/apm_api_integration/tests/transactions/throughput.ts b/x-pack/test/apm_api_integration/tests/transactions/throughput.ts index 040e280e3157f2..430392a32bfb82 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/throughput.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/throughput.ts @@ -20,7 +20,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { // url parameters const { start, end } = metadata; - const uiFilters = JSON.stringify({ environment: 'testing' }); + const uiFilters = JSON.stringify({}); registry.when('Throughput when data is not loaded', { config: 'basic', archives: [] }, () => { it('handles the empty state', async () => { @@ -28,6 +28,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { url.format({ pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, query: { + environment: 'testing', start, end, uiFilters, @@ -53,6 +54,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { url.format({ pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, query: { + environment: 'testing', start, end, uiFilters, From 481c92296e9114ac0b3297016c3f7df902ccd862 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Wed, 17 Feb 2021 12:53:33 -0800 Subject: [PATCH 06/93] [Alerts][Docs] Cleanup alerts README.md to remove duplication from docs (#91074) * Cleanup alerts README.md to remove duplication from docs * fixed due to comments * fixed due to comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/alerts/README.md | 290 +++++--------------------------- 1 file changed, 46 insertions(+), 244 deletions(-) diff --git a/x-pack/plugins/alerts/README.md b/x-pack/plugins/alerts/README.md index 2191b23eec11ee..aab848d4555d2d 100644 --- a/x-pack/plugins/alerts/README.md +++ b/x-pack/plugins/alerts/README.md @@ -20,26 +20,13 @@ Table of Contents - [Example](#example) - [Role Based Access-Control](#role-based-access-control) - [Alert Navigation](#alert-navigation) - - [RESTful API](#restful-api) - - [`POST /api/alerts/alert`: Create alert](#post-apialert-create-alert) - - [`DELETE /api/alerts/alert/{id}`: Delete alert](#delete-apialertid-delete-alert) - - [`GET /api/alerts/_find`: Find alerts](#get-apialertfind-find-alerts) - - [`GET /api/alerts/alert/{id}`: Get alert](#get-apialertid-get-alert) + - [Experimental RESTful API](#restful-api) - [`GET /api/alerts/alert/{id}/state`: Get alert state](#get-apialertidstate-get-alert-state) - [`GET /api/alerts/alert/{id}/_instance_summary`: Get alert instance summary](#get-apialertidstate-get-alert-instance-summary) - - [`GET /api/alerts/list_alert_types`: List alert types](#get-apialerttypes-list-alert-types) - - [`PUT /api/alerts/alert/{id}`: Update alert](#put-apialertid-update-alert) - - [`POST /api/alerts/alert/{id}/_enable`: Enable an alert](#post-apialertidenable-enable-an-alert) - - [`POST /api/alerts/alert/{id}/_disable`: Disable an alert](#post-apialertiddisable-disable-an-alert) - - [`POST /api/alerts/alert/{id}/_mute_all`: Mute all alert instances](#post-apialertidmuteall-mute-all-alert-instances) - - [`POST /api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`: Mute alert instance](#post-apialertalertidalertinstancealertinstanceidmute-mute-alert-instance) - - [`POST /api/alerts/alert/{id}/_unmute_all`: Unmute all alert instances](#post-apialertidunmuteall-unmute-all-alert-instances) - - [`POST /api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance](#post-apialertalertidalertinstancealertinstanceidunmute-unmute-an-alert-instance) - [`POST /api/alerts/alert/{id}/_update_api_key`: Update alert API key](#post-apialertidupdateapikey-update-alert-api-key) - - [Schedule Formats](#schedule-formats) - [Alert instance factory](#alert-instance-factory) - [Templating actions](#templating-actions) - - [Examples](#examples) + - [Examples](#examples) ## Terminology @@ -61,7 +48,7 @@ A Kibana alert detects a condition and executes one or more actions when that co 1. Develop and register an alert type (see alert types -> example). 2. Configure feature level privileges using RBAC -3. Create an alert using the RESTful API (see alerts -> create). +3. Create an alert using the RESTful API [Documentation](https://www.elastic.co/guide/en/kibana/master/alerts-api-update.html) (see alerts -> create). ## Limitations @@ -96,6 +83,7 @@ The following table describes the properties of the `options` object. |validate.params|When developing an alert type, you can choose to accept a series of parameters. You may also have the parameters validated before they are passed to the `executor` function or created as an alert saved object. In order to do this, provide a `@kbn/config-schema` schema that we will use to validate the `params` attribute.|@kbn/config-schema| |executor|This is where the code of the alert type lives. This is a function to be called when executing an alert on an interval basis. For full details, see executor section below.|Function| |producer|The id of the application producing this alert type.|string| +|minimumLicenseRequired|The value of a minimum license. Most of the alerts are licensed as "basic".|string| ### Executor @@ -142,13 +130,13 @@ This example receives server and threshold as parameters. It will read the CPU u ```typescript import { schema } from '@kbn/config-schema'; +import { AlertType, AlertExecutorOptions } from '../../../alerts/server'; import { - Alert, - AlertTypeParams, - AlertTypeState, - AlertInstanceState, - AlertInstanceContext -} from 'x-pack/plugins/alerts/common'; + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, +} from '../../../alerts/common'; ... interface MyAlertTypeParams extends AlertTypeParams { server: string; @@ -156,7 +144,7 @@ interface MyAlertTypeParams extends AlertTypeParams { } interface MyAlertTypeState extends AlertTypeState { - lastChecked: number; + lastChecked: Date; } interface MyAlertTypeInstanceState extends AlertInstanceState { @@ -257,83 +245,6 @@ const myAlertType: AlertType< server.newPlatform.setup.plugins.alerts.registerType(myAlertType); ``` -This example only receives threshold as a parameter. It will read the CPU usage of all the servers and schedule individual actions if the reading for a server is greater than the threshold. This is a better implementation than above as only one query is performed for all the servers instead of one query per server. - -```typescript -server.newPlatform.setup.plugins.alerts.registerType({ - id: 'my-alert-type', - name: 'My alert type', - validate: { - params: schema.object({ - threshold: schema.number({ min: 0, max: 1 }), - }), - }, - actionGroups: [ - { - id: 'default', - name: 'Default', - }, - ], - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - actionVariables: { - context: [ - { name: 'server', description: 'the server' }, - { name: 'hasCpuUsageIncreased', description: 'boolean indicating if the cpu usage has increased' }, - ], - state: [ - { name: 'cpuUsage', description: 'CPU usage' }, - ], - }, - async executor({ - alertId, - startedAt, - previousStartedAt, - services, - params, - state, - }: AlertExecutorOptions) { - const { threshold } = params; // Let's assume params is { threshold: 0.8 } - - // Call a function to get the CPU readings on all the servers. The result will be - // an array of { server, cpuUsage }. - const cpuUsageByServer = await getCpuUsageByServer(); - - for (const { server, cpuUsage: currentCpuUsage } of cpuUsageByServer) { - // Only execute if CPU usage is greater than threshold - if (currentCpuUsage > threshold) { - // The first argument is a unique identifier the alert instance is about. In this scenario - // the provided server will be used. Also, this id will be used to make `getState()` return - // previous state, if any, on matching identifiers. - const alertInstance = services.alertInstanceFactory(server); - - // State from last execution. This will exist if an alert instance was created and executed - // in the previous execution - const { cpuUsage: previousCpuUsage } = alertInstance.getState(); - - // Replace state entirely with new values - alertInstance.replaceState({ - cpuUsage: currentCpuUsage, - }); - - // 'default' refers to the id of a group of actions to be scheduled for execution, see 'actions' in create alert section - alertInstance.scheduleActions('default', { - server, - hasCpuUsageIncreased: currentCpuUsage > previousCpuUsage, - }); - } - } - - // Single object containing state that isn't specific to a server, this will become available - // within the `state` function parameter at the next execution - return { - lastChecked: new Date(), - }; - }, - producer: 'alerting', -}); -``` - ## Role Based Access-Control Once you have registered your AlertType, you need to grant your users privileges to use it. When registering a feature in Kibana you can specify multiple types of privileges which are granted to users when they're assigned certain roles. @@ -387,29 +298,37 @@ It's important to note that any role can be granted a mix of `all` and `read` pr ```typescript features.registerKibanaFeature({ - id: 'my-application-id', - name: 'My Application', - app: [], - privileges: { - all: { - alerting: { - all: [ - 'my-application-id.my-alert-type', - 'my-application-id.my-restricted-alert-type' - ], - }, - }, - read: { - alerting: { - all: [ - 'my-application-id.my-alert-type' - ] - read: [ - 'my-application-id.my-restricted-alert-type' - ], - }, - }, - }, + id: 'my-application-id', + name: 'My Application', + app: [], + privileges: { + all: { + app: ['my-application-id', 'kibana'], + savedObject: { + all: [], + read: [], + }, + ui: [], + api: [], + }, + read: { + app: ['lens', 'kibana'], + alerting: { + all: [ + 'my-application-id.my-alert-type' + ], + read: [ + 'my-application-id.my-restricted-alert-type' + ], + }, + savedObject: { + all: [], + read: [], + }, + ui: [], + api: [], + }, + }, }); ``` @@ -494,46 +413,10 @@ The only case in which this handler will not be used to evaluate the navigation You can use the `registerNavigation` api to specify as many AlertType specific handlers as you like, but you can only use it once per AlertType as we wouldn't know which handler to use if you specified two for the same AlertType. For the same reason, you can only use `registerDefaultNavigation` once per plugin, as it covers all cases for your specific plugin. -## RESTful API - -Using an alert type requires you to create an alert that will contain parameters and actions for a given alert type. See below for CRUD operations using the API. +## Experimental RESTful API -### `POST /api/alerts/alert`: Create alert - -Payload: - -|Property|Description|Type| -|---|---|---| -|enabled|Indicate if you want the alert to start executing on an interval basis after it has been created.|boolean| -|name|A name to reference and search in the future.|string| -|tags|A list of keywords to reference and search in the future.|string[]| -|alertTypeId|The id value of the alert type you want to call when the alert is scheduled to execute.|string| -|schedule|The schedule specifying when this alert should be run, using one of the available schedule formats specified under _Schedule Formats_ below|object| -|throttle|A Duration specifying 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 over this period.|string| -|params|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| -|actions|Array of the following:
- `group` (string): We support grouping actions in the scenario of escalations or different types of alert instances. If you don't need this, feel free to use `default` as a value.
- `id` (string): The id of the action saved object to execute.
- `params` (object): The map to the `params` the action type will receive. In order to help apply context to strings, we handle them as mustache templates and pass in a default set of context. (see templating actions).|array| - -### `DELETE /api/alerts/alert/{id}`: Delete alert - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to delete.|string| - -### `GET /api/alerts/_find`: Find alerts - -Params: - -See the saved objects API documentation for find. All the properties are the same except you cannot pass in `type`. - -### `GET /api/alerts/alert/{id}`: Get alert - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to get.|string| +Using of the alert type requires you to create an alert that will contain parameters and actions for a given alert type. API description for CRUD operations is a part of the [user documentation](https://www.elastic.co/guide/en/kibana/master/alerts-api-update.html). +API listed below is experimental and could be changed or removed in the future. ### `GET /api/alerts/alert/{id}/state`: Get alert state @@ -560,93 +443,12 @@ Query: |---|---|---| |dateStart|The date to start looking for alert events in the event log. Either an ISO date string, or a duration string indicating time since now.|string| -### `GET /api/alerts/list_alert_types`: List alert types - -No parameters. - -### `PUT /api/alerts/alert/{id}`: Update alert - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to update.|string| - -Payload: - -|Property|Description|Type| -|---|---|---| -|schedule|The schedule specifying when this alert should be run, using one of the available schedule formats specified under _Schedule Formats_ below|object| -|throttle|A Duration specifying 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 over this period.|string| -|name|A name to reference and search in the future.|string| -|tags|A list of keywords to reference and search in the future.|string[]| -|params|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| -|actions|Array of the following:
- `group` (string): We support grouping actions in the scenario of escalations or different types of alert instances. If you don't need this, feel free to use `default` as a value.
- `id` (string): The id of the action saved object to execute.
- `params` (object): There map to the `params` the action type will receive. In order to help apply context to strings, we handle them as mustache templates and pass in a default set of context. (see templating actions).|array| - -### `POST /api/alerts/alert/{id}/_enable`: Enable an alert - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to enable.|string| - -### `POST /api/alerts/alert/{id}/_disable`: Disable an alert - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to disable.|string| - -### `POST /api/alerts/alert/{id}/_mute_all`: Mute all alert instances - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to mute all alert instances for.|string| - -### `POST /api/alerts/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`: Mute alert instance - -Params: - -|Property|Description|Type| -|---|---|---| -|alertId|The id of the alert you're trying to mute an instance for.|string| -|alertInstanceId|The instance id of the alert instance you're trying to mute.|string| - -### `POST /api/alerts/alert/{id}/_unmute_all`: Unmute all alert instances - -Params: - -|Property|Description|Type| -|---|---|---| -|id|The id of the alert you're trying to unmute all alert instances for.|string| - -### `POST /api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`: Unmute an alert instance - -Params: - -|Property|Description|Type| -|---|---|---| -|alertId|The id of the alert you're trying to unmute an instance for.|string| -|alertInstanceId|The instance id of the alert instance you're trying to unmute.|string| - ### `POST /api/alerts/alert/{id}/_update_api_key`: Update alert API key |Property|Description|Type| |---|---|---| |id|The id of the alert you're trying to update the API key for. System will use user in request context to generate an API key for.|string| -## Schedule Formats -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. - ## Alert instance factory **alertInstanceFactory(id)** @@ -694,7 +496,7 @@ When an alert instance executes, the first argument is the `group` of actions to The templating engine is [mustache]. General definition for the [mustache variable] is a double-brace {{}}. All variables are HTML-escaped by default and if there is a requirement to render unescaped HTML, it should be applied the triple mustache: `{{{name}}}`. Also, can be used `&` to unescape a variable. -## Examples +### Examples The following code would be within an alert type. As you can see `cpuUsage ` will replace the state of the alert instance and `server` is the context for the alert instance to execute. The difference between the two is `cpuUsage ` will be accessible at the next execution. From 5eeae3dff4041686129edb3e186059901e3633e7 Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Wed, 17 Feb 2021 15:57:34 -0500 Subject: [PATCH 07/93] Update entity route schema, use more indices on detections page (#91718) --- .../common/endpoint/schema/resolver.ts | 2 +- .../components/graph_overlay/index.tsx | 17 +++++++---------- .../components/timeline/body/helpers.tsx | 2 +- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts index b38cabf33a3db0..3bfe2a7410c080 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts @@ -76,6 +76,6 @@ export const validateEntities = { /** * Indices to search in. */ - indices: schema.arrayOf(schema.string()), + indices: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]), }), }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx index 9c9c56461609d9..a8cfea1de8e74c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx @@ -30,8 +30,7 @@ import { TimelineId } from '../../../../common/types/timeline'; import { timelineSelectors } from '../../store/timeline'; import { timelineDefaults } from '../../store/timeline/defaults'; import { isFullScreen } from '../timeline/body/column_headers'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; -import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { sourcererSelectors } from '../../../common/store'; import { updateTimelineGraphEventId } from '../../../timelines/store/timeline/actions'; import { Resolver } from '../../../resolver/view'; import { @@ -169,14 +168,12 @@ const GraphOverlayComponent: React.FC = ({ isEventViewer, timelineId } globalFullScreen, ]); - let sourcereScope = SourcererScopeName.default; - if ([TimelineId.detectionsRulesDetailsPage, TimelineId.detectionsPage].includes(timelineId)) { - sourcereScope = SourcererScopeName.detections; - } else if (timelineId === TimelineId.active) { - sourcereScope = SourcererScopeName.timeline; - } + const existingIndexNamesSelector = useMemo( + () => sourcererSelectors.getAllExistingIndexNamesSelector(), + [] + ); + const existingIndexNames = useDeepEqualSelector(existingIndexNamesSelector); - const { selectedPatterns } = useSourcererScope(sourcereScope); return ( = ({ isEventViewer, timelineId } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index 32a01654bf9aff..dd701aa284997e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx @@ -151,7 +151,7 @@ const InvestigateInResolverActionComponent: React.FC !isInvestigateInResolverActionEnabled(ecsData), [ecsData]); const handleClick = useCallback(() => { dispatch(updateTimelineGraphEventId({ id: timelineId, graphEventId: ecsData._id })); - if (TimelineId.active) { + if (timelineId === TimelineId.active) { dispatch(setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.graph })); } }, [dispatch, ecsData._id, timelineId]); From 3e5497484d8c3a034e834a56128c93829b764ad9 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 17 Feb 2021 14:02:20 -0700 Subject: [PATCH 08/93] [Maps] use stored map buffer to generate queries for search sessions (#91148) * [Maps] use stored map buffer to generate queries for search sessions * getDataFilters unit test * tslint * update setQuery unit tests * only set searchSessionMapBuffer when search session isRestore * tslint Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../maps/public/actions/map_actions.test.js | 3 + .../maps/public/actions/map_actions.ts | 5 ++ .../maps/public/embeddable/map_embeddable.tsx | 50 ++++++---------- .../plugins/maps/public/embeddable/types.ts | 3 +- x-pack/plugins/maps/public/reducers/map.d.ts | 1 + x-pack/plugins/maps/public/reducers/map.js | 3 +- .../public/selectors/map_selectors.test.ts | 60 ++++++++++++++++++- .../maps/public/selectors/map_selectors.ts | 9 ++- 8 files changed, 96 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/maps/public/actions/map_actions.test.js b/x-pack/plugins/maps/public/actions/map_actions.test.js index c0ad934c232e22..fafafa6b6a0711 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.test.js +++ b/x-pack/plugins/maps/public/actions/map_actions.test.js @@ -277,6 +277,9 @@ describe('map_actions', () => { require('../selectors/map_selectors').getSearchSessionId = () => { return searchSessionId; }; + require('../selectors/map_selectors').getSearchSessionMapBuffer = () => { + return undefined; + }; require('../selectors/map_selectors').getMapSettings = () => { return { autoFitToDataBounds: false, diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index 33c79c793974b4..9682306852ba92 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -22,6 +22,7 @@ import { getTimeFilters, getLayerList, getSearchSessionId, + getSearchSessionMapBuffer, } from '../selectors/map_selectors'; import { CLEAR_GOTO, @@ -229,12 +230,14 @@ export function setQuery({ filters = [], forceRefresh = false, searchSessionId, + searchSessionMapBuffer, }: { filters?: Filter[]; query?: Query; timeFilters?: TimeRange; forceRefresh?: boolean; searchSessionId?: string; + searchSessionMapBuffer?: MapExtent; }) { return async ( dispatch: ThunkDispatch, @@ -255,6 +258,7 @@ export function setQuery({ }, filters: filters ? filters : getFilters(getState()), searchSessionId, + searchSessionMapBuffer, }; const prevQueryContext = { @@ -262,6 +266,7 @@ export function setQuery({ query: getQuery(getState()), filters: getFilters(getState()), searchSessionId: getSearchSessionId(getState()), + searchSessionMapBuffer: getSearchSessionMapBuffer(getState()), }; if (_.isEqual(nextQueryContext, prevQueryContext)) { diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index f42a055b24d0a2..b7e50815fd1f71 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -44,6 +44,7 @@ import { } from '../reducers/non_serializable_instances'; import { getMapCenter, + getMapBuffer, getMapZoom, getHiddenLayerIds, getQueryableUniqueIndexPatternIds, @@ -151,11 +152,7 @@ export class MapEmbeddable store.dispatch(disableScrollZoom()); this._dispatchSetQuery({ - query: this.input.query, - timeRange: this.input.timeRange, - filters: this.input.filters, forceRefresh: false, - searchSessionId: this.input.searchSessionId, }); if (this.input.refreshConfig) { this._dispatchSetRefreshConfig(this.input.refreshConfig); @@ -230,11 +227,7 @@ export class MapEmbeddable this.input.searchSessionId !== this._prevSearchSessionId ) { this._dispatchSetQuery({ - query: this.input.query, - timeRange: this.input.timeRange, - filters: this.input.filters, forceRefresh: false, - searchSessionId: this.input.searchSessionId, }); } @@ -258,30 +251,24 @@ export class MapEmbeddable } } - _dispatchSetQuery({ - query, - timeRange, - filters = [], - forceRefresh, - searchSessionId, - }: { - query?: Query; - timeRange?: TimeRange; - filters?: Filter[]; - forceRefresh: boolean; - searchSessionId?: string; - }) { - this._prevTimeRange = timeRange; - this._prevQuery = query; - this._prevFilters = filters; - this._prevSearchSessionId = searchSessionId; + _dispatchSetQuery({ forceRefresh }: { forceRefresh: boolean }) { + this._prevTimeRange = this.input.timeRange; + this._prevQuery = this.input.query; + this._prevFilters = this.input.filters; + this._prevSearchSessionId = this.input.searchSessionId; + const enabledFilters = this.input.filters + ? this.input.filters.filter((filter) => !filter.meta.disabled) + : []; this._savedMap.getStore().dispatch( setQuery({ - filters: filters.filter((filter) => !filter.meta.disabled), - query, - timeFilters: timeRange, + filters: enabledFilters, + query: this.input.query, + timeFilters: this.input.timeRange, forceRefresh, - searchSessionId, + searchSessionId: this.input.searchSessionId, + searchSessionMapBuffer: getIsRestore(this.input.searchSessionId) + ? this.input.mapBuffer + : undefined, }) ); } @@ -432,11 +419,7 @@ export class MapEmbeddable reload() { this._dispatchSetQuery({ - query: this.input.query, - timeRange: this.input.timeRange, - filters: this.input.filters, forceRefresh: true, - searchSessionId: this.input.searchSessionId, }); } @@ -457,6 +440,7 @@ export class MapEmbeddable lon: center.lon, zoom, }, + mapBuffer: getMapBuffer(this._savedMap.getStore().getState()), }); } diff --git a/x-pack/plugins/maps/public/embeddable/types.ts b/x-pack/plugins/maps/public/embeddable/types.ts index 67489802bc31d1..7cd4fa8e1253bd 100644 --- a/x-pack/plugins/maps/public/embeddable/types.ts +++ b/x-pack/plugins/maps/public/embeddable/types.ts @@ -12,7 +12,7 @@ import { SavedObjectEmbeddableInput, } from '../../../../../src/plugins/embeddable/public'; import { RefreshInterval, Query, Filter, TimeRange } from '../../../../../src/plugins/data/common'; -import { MapCenterAndZoom } from '../../common/descriptor_types'; +import { MapCenterAndZoom, MapExtent } from '../../common/descriptor_types'; import { MapSavedObjectAttributes } from '../../common/map_saved_object_type'; import { MapSettings } from '../reducers/map'; @@ -25,6 +25,7 @@ interface MapEmbeddableState { isLayerTOCOpen?: boolean; openTOCDetails?: string[]; mapCenter?: MapCenterAndZoom; + mapBuffer?: MapExtent; mapSettings?: Partial; hiddenLayers?: string[]; hideFilterActions?: boolean; diff --git a/x-pack/plugins/maps/public/reducers/map.d.ts b/x-pack/plugins/maps/public/reducers/map.d.ts index 6a51d4feeb9dfe..1cf37561609640 100644 --- a/x-pack/plugins/maps/public/reducers/map.d.ts +++ b/x-pack/plugins/maps/public/reducers/map.d.ts @@ -37,6 +37,7 @@ export type MapContext = { refreshTimerLastTriggeredAt?: string; drawState?: DrawState; searchSessionId?: string; + searchSessionMapBuffer?: MapExtent; }; export type MapSettings = { diff --git a/x-pack/plugins/maps/public/reducers/map.js b/x-pack/plugins/maps/public/reducers/map.js index fa7e1308bac4f4..9bf0df612bac40 100644 --- a/x-pack/plugins/maps/public/reducers/map.js +++ b/x-pack/plugins/maps/public/reducers/map.js @@ -241,7 +241,7 @@ export function map(state = DEFAULT_MAP_STATE, action) { }; return { ...state, mapState: { ...state.mapState, ...newMapState } }; case SET_QUERY: - const { query, timeFilters, filters, searchSessionId } = action; + const { query, timeFilters, filters, searchSessionId, searchSessionMapBuffer } = action; return { ...state, mapState: { @@ -250,6 +250,7 @@ export function map(state = DEFAULT_MAP_STATE, action) { timeFilters, filters, searchSessionId, + searchSessionMapBuffer, }, }; case SET_REFRESH_CONFIG: diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts index 89cd80f4daab50..fff33ae246b319 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts @@ -26,9 +26,67 @@ jest.mock('../kibana_services', () => ({ })); import { DEFAULT_MAP_STORE_STATE } from '../reducers/store'; -import { areLayersLoaded, getTimeFilters } from './map_selectors'; +import { areLayersLoaded, getDataFilters, getTimeFilters } from './map_selectors'; import { LayerDescriptor } from '../../common/descriptor_types'; import { ILayer } from '../classes/layers/layer'; +import { Filter } from '../../../../../src/plugins/data/public'; + +describe('getDataFilters', () => { + const mapExtent = { + maxLat: 1, + maxLon: 1, + minLat: 0, + minLon: 0, + }; + const mapBuffer = { + maxLat: 1.5, + maxLon: 1.5, + minLat: -0.5, + minLon: -0.5, + }; + const mapZoom = 4; + const timeFilters = { to: '2001-01-01', from: '2001-12-31' }; + const refreshTimerLastTriggeredAt = '2001-01-01T00:00:00'; + const query = undefined; + const filters: Filter[] = []; + const searchSessionId = '12345'; + const searchSessionMapBuffer = { + maxLat: 1.25, + maxLon: 1.25, + minLat: -0.25, + minLon: -0.25, + }; + + test('should set buffer as searchSessionMapBuffer when using searchSessionId', () => { + const dataFilters = getDataFilters.resultFunc( + mapExtent, + mapBuffer, + mapZoom, + timeFilters, + refreshTimerLastTriggeredAt, + query, + filters, + searchSessionId, + searchSessionMapBuffer + ); + expect(dataFilters.buffer).toEqual(searchSessionMapBuffer); + }); + + test('should fall back to screen buffer when using searchSessionId and searchSessionMapBuffer is not provided', () => { + const dataFilters = getDataFilters.resultFunc( + mapExtent, + mapBuffer, + mapZoom, + timeFilters, + refreshTimerLastTriggeredAt, + query, + filters, + searchSessionId, + undefined + ); + expect(dataFilters.buffer).toEqual(mapBuffer); + }); +}); describe('getTimeFilters', () => { test('should return timeFilters when contained in state', () => { diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index b16ac704c3715b..ffaff3263cf484 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -183,6 +183,9 @@ export const getFilters = ({ map }: MapStoreState): Filter[] => map.mapState.fil export const getSearchSessionId = ({ map }: MapStoreState): string | undefined => map.mapState.searchSessionId; +export const getSearchSessionMapBuffer = ({ map }: MapStoreState): MapExtent | undefined => + map.mapState.searchSessionMapBuffer; + export const isUsingSearch = (state: MapStoreState): boolean => { const filters = getFilters(state).filter((filter) => !filter.meta.disabled); const queryString = _.get(getQuery(state), 'query', ''); @@ -235,6 +238,7 @@ export const getDataFilters = createSelector( getQuery, getFilters, getSearchSessionId, + getSearchSessionMapBuffer, ( mapExtent, mapBuffer, @@ -243,11 +247,12 @@ export const getDataFilters = createSelector( refreshTimerLastTriggeredAt, query, filters, - searchSessionId + searchSessionId, + searchSessionMapBuffer ) => { return { extent: mapExtent, - buffer: mapBuffer, + buffer: searchSessionId && searchSessionMapBuffer ? searchSessionMapBuffer : mapBuffer, zoom: mapZoom, timeFilters, refreshTimerLastTriggeredAt, From 5bbbcb358f77754f6ee0dfc2db3b9c5ccd978364 Mon Sep 17 00:00:00 2001 From: Diana Derevyankina <54894989+DziyanaDzeraviankina@users.noreply.github.com> Date: Thu, 18 Feb 2021 00:04:38 +0300 Subject: [PATCH 09/93] Replace EuiCodeBlock with Monaco editor in Discover (#90781) * Update version of react-monaco-editor and monaco-editor libraries * Fix yarn lock file * Fix CI * Fix unit tests * Fix CI * Fix comment * move monaco instance in window.MonacoEnvironment * Replace EuiCodeBlock with Monaco editor in Discover expanded document * Replace EuiCodeBlock with EuiErrorBoundary * Revert changes done by mistake * Remove unused translations * Fix doc_viewer test and snapshots * Add comment and rename the function related to editor height calculation * Remove "value" from JSON tree and fix resizing * Update json_code_editor.test.tsx.snap * Fix JsonCodeEditor props * Fix json_code_editor test * Delete JsonCodeBlock and remove inline style in JsonCodeEditor * Rename jsonCodeEditor CSS class name to dscJsonCodeEditor Co-authored-by: Uladzislau Lasitsa Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/doc_viewer/doc_viewer.test.tsx | 5 ++ .../doc_viewer/doc_viewer_render_error.tsx | 20 ++--- .../components/doc_viewer/doc_viewer_tab.tsx | 6 +- .../json_code_block.test.tsx.snap | 20 ----- .../json_code_block/json_code_block.tsx | 23 ----- .../json_code_editor.test.tsx.snap | 71 ++++++++++++++++ .../json_code_editor/json_code_editor.scss | 3 + .../json_code_editor.test.tsx} | 18 ++-- .../json_code_editor/json_code_editor.tsx | 84 +++++++++++++++++++ src/plugins/discover/public/plugin.ts | 4 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 12 files changed, 187 insertions(+), 69 deletions(-) delete mode 100644 src/plugins/discover/public/application/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap delete mode 100644 src/plugins/discover/public/application/components/json_code_block/json_code_block.tsx create mode 100644 src/plugins/discover/public/application/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap create mode 100644 src/plugins/discover/public/application/components/json_code_editor/json_code_editor.scss rename src/plugins/discover/public/application/components/{json_code_block/json_code_block.test.tsx => json_code_editor/json_code_editor.test.tsx} (53%) create mode 100644 src/plugins/discover/public/application/components/json_code_editor/json_code_editor.tsx diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx index 62e19b83b016e4..6afa7f89371f98 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.test.tsx @@ -16,6 +16,11 @@ import { DocViewRenderProps } from '../../doc_views/doc_views_types'; jest.mock('../../../kibana_services', () => { let registry: any[] = []; return { + getServices: () => ({ + uiSettings: { + get: jest.fn(), + }, + }), getDocViewsRegistry: () => ({ addDocView(view: any) { registry.push(view); diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_error.tsx b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_error.tsx index 571af94f293728..b9b068ce4bd073 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_error.tsx +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_render_error.tsx @@ -7,20 +7,18 @@ */ import React from 'react'; -import { EuiCallOut, EuiCodeBlock } from '@elastic/eui'; -import { formatMsg, formatStack } from '../../../../../kibana_legacy/public'; +import { EuiErrorBoundary } from '@elastic/eui'; interface Props { error: Error | string; } -export function DocViewerError({ error }: Props) { - const errMsg = formatMsg(error); - const errStack = typeof error === 'object' ? formatStack(error) : ''; +const DocViewerErrorWrapper = ({ error }: Props) => { + throw error; +}; - return ( - - {errStack && {errStack}} - - ); -} +export const DocViewerError = ({ error }: Props) => ( + + + +); diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_tab.tsx b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_tab.tsx index 1da75b45239105..25454a3bad38ab 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_tab.tsx +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer_tab.tsx @@ -11,6 +11,8 @@ import { I18nProvider } from '@kbn/i18n/react'; import { DocViewRenderTab } from './doc_viewer_render_tab'; import { DocViewerError } from './doc_viewer_render_error'; import { DocViewRenderFn, DocViewRenderProps } from '../../doc_views/doc_views_types'; +import { getServices } from '../../../kibana_services'; +import { KibanaContextProvider } from '../../../../../kibana_react/public'; interface Props { component?: React.ComponentType; @@ -72,7 +74,9 @@ export class DocViewerTab extends React.Component { const Component = component as any; return ( - + + + ); } diff --git a/src/plugins/discover/public/application/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap b/src/plugins/discover/public/application/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap deleted file mode 100644 index d6f48a9b3c7745..00000000000000 --- a/src/plugins/discover/public/application/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap +++ /dev/null @@ -1,20 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`returns the \`JsonCodeEditor\` component 1`] = ` - - { - "_index": "test", - "_type": "doc", - "_id": "foo", - "_score": 1, - "_source": { - "test": 123 - } -} - -`; diff --git a/src/plugins/discover/public/application/components/json_code_block/json_code_block.tsx b/src/plugins/discover/public/application/components/json_code_block/json_code_block.tsx deleted file mode 100644 index 7dab2b199b018f..00000000000000 --- a/src/plugins/discover/public/application/components/json_code_block/json_code_block.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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 React from 'react'; -import { EuiCodeBlock } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { DocViewRenderProps } from '../../doc_views/doc_views_types'; - -export function JsonCodeBlock({ hit }: DocViewRenderProps) { - const label = i18n.translate('discover.docViews.json.codeEditorAriaLabel', { - defaultMessage: 'Read only JSON view of an elasticsearch document', - }); - return ( - - {JSON.stringify(hit, null, 2)} - - ); -} diff --git a/src/plugins/discover/public/application/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap b/src/plugins/discover/public/application/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap new file mode 100644 index 00000000000000..4f27158eee04f1 --- /dev/null +++ b/src/plugins/discover/public/application/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`returns the \`JsonCodeEditor\` component 1`] = ` + + + +
+ + + +
+
+ + + +
+`; diff --git a/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.scss b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.scss new file mode 100644 index 00000000000000..5521df5b363acd --- /dev/null +++ b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.scss @@ -0,0 +1,3 @@ +.dscJsonCodeEditor { + width: 100% +} diff --git a/src/plugins/discover/public/application/components/json_code_block/json_code_block.test.tsx b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.test.tsx similarity index 53% rename from src/plugins/discover/public/application/components/json_code_block/json_code_block.test.tsx rename to src/plugins/discover/public/application/components/json_code_editor/json_code_editor.test.tsx index dd56a1077f1ac6..4ccb3010d5a2b9 100644 --- a/src/plugins/discover/public/application/components/json_code_block/json_code_block.test.tsx +++ b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.test.tsx @@ -8,17 +8,15 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { JsonCodeBlock } from './json_code_block'; -import { IndexPattern } from '../../../../../data/public'; +import { JsonCodeEditor } from './json_code_editor'; it('returns the `JsonCodeEditor` component', () => { - const props = { - hit: { _index: 'test', _type: 'doc', _id: 'foo', _score: 1, _source: { test: 123 } }, - columns: [], - indexPattern: {} as IndexPattern, - filter: jest.fn(), - onAddColumn: jest.fn(), - onRemoveColumn: jest.fn(), + const value = { + _index: 'test', + _type: 'doc', + _id: 'foo', + _score: 1, + _source: { test: 123 }, }; - expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); }); diff --git a/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.tsx b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.tsx new file mode 100644 index 00000000000000..85d6aad7552502 --- /dev/null +++ b/src/plugins/discover/public/application/components/json_code_editor/json_code_editor.tsx @@ -0,0 +1,84 @@ +/* + * 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 './json_code_editor.scss'; + +import React, { useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { monaco, XJsonLang } from '@kbn/monaco'; +import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { CodeEditor } from '../../../../../kibana_react/public'; +import { DocViewRenderProps } from '../../../application/doc_views/doc_views_types'; + +const codeEditorAriaLabel = i18n.translate('discover.json.codeEditorAriaLabel', { + defaultMessage: 'Read only JSON view of an elasticsearch document', +}); +const copyToClipboardLabel = i18n.translate('discover.json.copyToClipboardLabel', { + defaultMessage: 'Copy to clipboard', +}); + +export const JsonCodeEditor = ({ hit }: DocViewRenderProps) => { + const jsonValue = JSON.stringify(hit, null, 2); + + // setting editor height based on lines height and count to stretch and fit its content + const setEditorCalculatedHeight = useCallback((editor) => { + const editorElement = editor.getDomNode(); + + if (!editorElement) { + return; + } + + const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight); + const lineCount = editor.getModel()?.getLineCount() || 1; + const height = editor.getTopForLineNumber(lineCount + 1) + lineHeight; + + editorElement.style.height = `${height}px`; + editor.layout(); + }, []); + + return ( + + + +
+ + {(copy) => ( + + {copyToClipboardLabel} + + )} + +
+
+ + {}} + editorDidMount={setEditorCalculatedHeight} + aria-label={codeEditorAriaLabel} + options={{ + automaticLayout: true, + fontSize: 12, + minimap: { + enabled: false, + }, + overviewRulerBorder: false, + readOnly: true, + scrollbar: { + alwaysConsumeMouseWheel: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + }} + /> + +
+ ); +}; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index c53dfaff24112f..47161c2b8298e1 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -36,7 +36,7 @@ import { UrlGeneratorState } from '../../share/public'; import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types'; import { DocViewsRegistry } from './application/doc_views/doc_views_registry'; import { DocViewTable } from './application/components/table/table'; -import { JsonCodeBlock } from './application/components/json_code_block/json_code_block'; +import { JsonCodeEditor } from './application/components/json_code_editor/json_code_editor'; import { setDocViewsRegistry, setUrlTracker, @@ -187,7 +187,7 @@ export class DiscoverPlugin defaultMessage: 'JSON', }), order: 20, - component: JsonCodeBlock, + component: JsonCodeEditor, }); const { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3196bcc6031c9e..3a07e303eb7d81 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1479,7 +1479,6 @@ "discover.docTable.tableRow.viewSingleDocumentLinkText": "単一のドキュメントを表示", "discover.docTable.tableRow.viewSurroundingDocumentsLinkText": "周りのドキュメントを表示", "discover.documentsAriaLabel": "ドキュメント", - "discover.docViews.json.codeEditorAriaLabel": "Elasticsearch ドキュメントの JSON ビューのみを読み込む", "discover.docViews.json.jsonTitle": "JSON", "discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", "discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "{underscoreSign} で始まるフィールド名はサポートされません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7292769cdbd0fa..71a2fd83be9680 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1479,7 +1479,6 @@ "discover.docTable.tableRow.viewSingleDocumentLinkText": "查看单个文档", "discover.docTable.tableRow.viewSurroundingDocumentsLinkText": "查看周围文档", "discover.documentsAriaLabel": "文档", - "discover.docViews.json.codeEditorAriaLabel": "Elasticsearch 文档的只读 JSON 视图", "discover.docViews.json.jsonTitle": "JSON", "discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", "discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "不支持以 {underscoreSign} 开头的字段名称", From a2cd3662b20937b04666b2f48a58abaea9e7a3d8 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 17 Feb 2021 16:23:30 -0500 Subject: [PATCH 10/93] [Maps] rename data load method for clarity (#91547) --- .../maps/public/classes/layers/layer.tsx | 4 ++-- .../layers/vector_layer/vector_layer.tsx | 2 +- .../public/selectors/map_selectors.test.ts | 20 ++++++++++--------- .../maps/public/selectors/map_selectors.ts | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index 89c6d70a217c95..e3a21b596afe14 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -77,7 +77,7 @@ export interface ILayer { canShowTooltip(): boolean; syncLayerWithMB(mbMap: MbMap): void; getLayerTypeIconName(): string; - isDataLoaded(): boolean; + isInitialDataLoadComplete(): boolean; getIndexPatternIds(): string[]; getQueryableIndexPatternIds(): string[]; getType(): string | undefined; @@ -446,7 +446,7 @@ export class AbstractLayer implements ILayer { throw new Error('should implement Layer#getLayerTypeIconName'); } - isDataLoaded(): boolean { + isInitialDataLoadComplete(): boolean { const sourceDataRequest = this.getSourceDataRequest(); return sourceDataRequest ? sourceDataRequest.hasData() : false; } diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 7e87d99fd4f93f..2373ed3ba2062f 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -174,7 +174,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { return this.getValidJoins().length > 0; } - isDataLoaded() { + isInitialDataLoadComplete() { const sourceDataRequest = this.getSourceDataRequest(); if (!sourceDataRequest || !sourceDataRequest.hasData()) { return false; diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts index fff33ae246b319..58268b6ea9d82c 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts @@ -124,12 +124,12 @@ describe('getTimeFilters', () => { describe('areLayersLoaded', () => { function createLayerMock({ hasErrors = false, - isDataLoaded = false, + isInitialDataLoadComplete = false, isVisible = true, showAtZoomLevel = true, }: { hasErrors?: boolean; - isDataLoaded?: boolean; + isInitialDataLoadComplete?: boolean; isVisible?: boolean; showAtZoomLevel?: boolean; }) { @@ -137,8 +137,8 @@ describe('areLayersLoaded', () => { hasErrors: () => { return hasErrors; }, - isDataLoaded: () => { - return isDataLoaded; + isInitialDataLoadComplete: () => { + return isInitialDataLoadComplete; }, isVisible: () => { return isVisible; @@ -157,35 +157,37 @@ describe('areLayersLoaded', () => { }); test('layer should not be counted as loaded if it has not loaded', () => { - const layerList = [createLayerMock({ isDataLoaded: false })]; + const layerList = [createLayerMock({ isInitialDataLoadComplete: false })]; const waitingForMapReadyLayerList: LayerDescriptor[] = []; const zoom = 4; expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(false); }); test('layer should be counted as loaded if its not visible', () => { - const layerList = [createLayerMock({ isVisible: false, isDataLoaded: false })]; + const layerList = [createLayerMock({ isVisible: false, isInitialDataLoadComplete: false })]; const waitingForMapReadyLayerList: LayerDescriptor[] = []; const zoom = 4; expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); }); test('layer should be counted as loaded if its not shown at zoom level', () => { - const layerList = [createLayerMock({ showAtZoomLevel: false, isDataLoaded: false })]; + const layerList = [ + createLayerMock({ showAtZoomLevel: false, isInitialDataLoadComplete: false }), + ]; const waitingForMapReadyLayerList: LayerDescriptor[] = []; const zoom = 4; expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); }); test('layer should be counted as loaded if it has a loading error', () => { - const layerList = [createLayerMock({ hasErrors: true, isDataLoaded: false })]; + const layerList = [createLayerMock({ hasErrors: true, isInitialDataLoadComplete: false })]; const waitingForMapReadyLayerList: LayerDescriptor[] = []; const zoom = 4; expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); }); test('layer should be counted as loaded if its loaded', () => { - const layerList = [createLayerMock({ isDataLoaded: true })]; + const layerList = [createLayerMock({ isInitialDataLoadComplete: true })]; const waitingForMapReadyLayerList: LayerDescriptor[] = []; const zoom = 4; expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index ffaff3263cf484..35c73a2bd2f1c7 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -437,7 +437,7 @@ export const areLayersLoaded = createSelector( layer.isVisible() && layer.showAtZoomLevel(zoom) && !layer.hasErrors() && - !layer.isDataLoaded() + !layer.isInitialDataLoadComplete() ) { return false; } From e095a6abf2ed3c7d49322643382b2056c89090d4 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Wed, 17 Feb 2021 16:26:40 -0500 Subject: [PATCH 11/93] [Security Solution][Timeline] Minor side panel fixes (#91691) --- .../host_overview/endpoint_overview/translations.ts | 4 ++-- .../side_panel/host_details/expandable_host.tsx | 11 +++++++++-- .../network_details/expandable_network.tsx | 11 +++++++++-- .../timeline/body/events/stateful_event.tsx | 12 +++++++++--- x-pack/plugins/translations/translations/ja-JP.json | 2 -- x-pack/plugins/translations/translations/zh-CN.json | 2 -- 6 files changed, 29 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts index 3e18c7a01c808e..1a007cd7f0f56d 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/translations.ts @@ -17,13 +17,13 @@ export const ENDPOINT_POLICY = i18n.translate( export const POLICY_STATUS = i18n.translate( 'xpack.securitySolution.host.details.endpoint.policyStatus', { - defaultMessage: 'Configuration Status', + defaultMessage: 'Policy Status', } ); export const SENSORVERSION = i18n.translate( 'xpack.securitySolution.host.details.endpoint.sensorversion', { - defaultMessage: 'Sensorversion', + defaultMessage: 'Sensor Version', } ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx index 4e101e29bb4841..8fce9a186bbd46 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { EuiTitle } from '@elastic/eui'; import { HostDetailsLink } from '../../../../common/components/links'; @@ -23,14 +24,20 @@ interface ExpandableHostProps { hostName: string; } +const StyledTitle = styled.h4` + word-break: break-all; + word-wrap: break-word; + white-space: pre-wrap; +`; + export const ExpandableHostDetailsTitle = ({ hostName }: ExpandableHostProps) => ( -

+ {i18n.translate('xpack.securitySolution.timeline.sidePanel.hostDetails.title', { defaultMessage: 'Host details', })} {`: ${hostName}`} -

+
); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx index b12b575681acf2..19f6e2c9652f92 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx @@ -7,6 +7,7 @@ import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { FlowTarget } from '../../../../../common/search_strategy'; @@ -31,14 +32,20 @@ interface ExpandableNetworkProps { expandedNetwork: { ip: string; flowTarget: FlowTarget }; } +const StyledTitle = styled.h4` + word-break: break-all; + word-wrap: break-word; + white-space: pre-wrap; +`; + export const ExpandableNetworkDetailsTitle = ({ ip }: { ip: string }) => ( -

+ {i18n.translate('xpack.securitySolution.timeline.sidePanel.networkDetails.title', { defaultMessage: 'Network details', })} {`: ${ip}`} -

+
); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 45b10d635195bb..4191badd6b03fb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -109,8 +109,14 @@ const StatefulEventComponent: React.FC = ({ }, [event?.data]); const hostIPAddresses = useMemo(() => { - const ipList = getMappedNonEcsValue({ data: event?.data, fieldName: 'host.ip' }); - return ipList; + const hostIpList = getMappedNonEcsValue({ data: event?.data, fieldName: 'host.ip' }) ?? []; + const sourceIpList = getMappedNonEcsValue({ data: event?.data, fieldName: 'source.ip' }) ?? []; + const destinationIpList = + getMappedNonEcsValue({ + data: event?.data, + fieldName: 'destination.ip', + }) ?? []; + return new Set([...hostIpList, ...sourceIpList, ...destinationIpList]); }, [event?.data]); const activeTab = tabType ?? TimelineTabs.query; @@ -123,7 +129,7 @@ const StatefulEventComponent: React.FC = ({ activeExpandedDetail?.params?.hostName === hostName) || (activeExpandedDetail?.panelView === 'networkDetail' && activeExpandedDetail?.params?.ip && - hostIPAddresses?.includes(activeExpandedDetail?.params?.ip)) || + hostIPAddresses?.has(activeExpandedDetail?.params?.ip)) || false; const getNotesByIds = useMemo(() => appSelectors.notesByIdsSelector(), []); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3a07e303eb7d81..344a07e53e3edb 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18875,8 +18875,6 @@ "xpack.securitySolution.hooks.useAddToTimeline.addedFieldMessage": "{fieldOrValue}をタイムラインに追加しました", "xpack.securitySolution.host.details.architectureLabel": "アーキテクチャー", "xpack.securitySolution.host.details.endpoint.endpointPolicy": "統合", - "xpack.securitySolution.host.details.endpoint.policyStatus": "構成ステータス", - "xpack.securitySolution.host.details.endpoint.sensorversion": "センサーバージョン", "xpack.securitySolution.host.details.firstSeenTitle": "初回の認識", "xpack.securitySolution.host.details.lastSeenTitle": "前回の認識", "xpack.securitySolution.host.details.overview.cloudProviderTitle": "クラウドプロバイダー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 71a2fd83be9680..579a06d44e6595 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18921,8 +18921,6 @@ "xpack.securitySolution.hooks.useAddToTimeline.addedFieldMessage": "已将 {fieldOrValue} 添加到时间线", "xpack.securitySolution.host.details.architectureLabel": "架构", "xpack.securitySolution.host.details.endpoint.endpointPolicy": "集成", - "xpack.securitySolution.host.details.endpoint.policyStatus": "配置状态", - "xpack.securitySolution.host.details.endpoint.sensorversion": "感应器版本", "xpack.securitySolution.host.details.firstSeenTitle": "首次看到时间", "xpack.securitySolution.host.details.lastSeenTitle": "最后看到时间", "xpack.securitySolution.host.details.overview.cloudProviderTitle": "云服务提供商", From e6ecc9fe255b69f6f950ba54153d707bbfdd1e0a Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Wed, 17 Feb 2021 17:08:41 -0500 Subject: [PATCH 12/93] [Security Solution] [Detections] Prevent early ejection from big loop when index pattern is missing the given timestamp override field (#91597) * fix for when search response yields 400 with missing timestamp override field * prefer includes over strict equality * adds integration test to check for this case * adds a unit test and util function to ensure unit test executes properly and waits for rule to complete running * remove comments from rebase --- .../signals/single_search_after.ts | 28 ++++++++++++++++ .../lib/detection_engine/signals/utils.ts | 12 ++++--- .../security_and_spaces/tests/create_rules.ts | 32 +++++++++++++++++++ .../detection_engine_api_integration/utils.ts | 13 ++++++++ 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts index fbea610428bd0f..9c58bba296ad35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts @@ -87,6 +87,34 @@ export const singleSearchAfter = async ({ }; } catch (exc) { logger.error(buildRuleMessage(`[-] nextSearchAfter threw an error ${exc}`)); + if ( + exc.message.includes('No mapping found for [@timestamp] in order to sort on') || + exc.message.includes(`No mapping found for [${timestampOverride}] in order to sort on`) + ) { + logger.error(buildRuleMessage(`[-] failure reason: ${exc.message}`)); + + const searchRes: SignalSearchResponse = { + took: 0, + timed_out: false, + _shards: { + total: 1, + successful: 1, + failed: 0, + skipped: 0, + }, + hits: { + total: 0, + max_score: 0, + hits: [], + }, + }; + return { + searchResult: searchRes, + searchDuration: '-1.0', + searchErrors: exc.message, + }; + } + throw exc; } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index f6bd5c8a325be1..323986e6ffecbc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -134,9 +134,10 @@ export const hasTimestampFields = async ( ? 'timestamp field "@timestamp"' : `timestamp override field "${timestampField}"` }: ${JSON.stringify( - isEmpty(timestampFieldCapsResponse.body.fields) + isEmpty(timestampFieldCapsResponse.body.fields) || + isEmpty(timestampFieldCapsResponse.body.fields[timestampField]) ? timestampFieldCapsResponse.body.indices - : timestampFieldCapsResponse.body.fields[timestampField].unmapped.indices + : timestampFieldCapsResponse.body.fields[timestampField]?.unmapped?.indices )}`; logger.error(buildRuleMessage(errorString)); await ruleStatusService.warning(errorString); @@ -698,9 +699,12 @@ export const createSearchAfterReturnTypeFromResponse = ({ searchResult._shards.failed === 0 || searchResult._shards.failures?.every((failure) => { return ( - failure.reason?.reason === 'No mapping found for [@timestamp] in order to sort on' || - failure.reason?.reason === + failure.reason?.reason?.includes( + 'No mapping found for [@timestamp] in order to sort on' + ) || + failure.reason?.reason?.includes( `No mapping found for [${timestampOverride}] in order to sort on` + ) ); }), lastLookBackDate: lastValidDate({ searchResult, timestampOverride }), diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts index a9120bde274f24..8d494724d9f766 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts @@ -27,11 +27,13 @@ import { getSimpleMlRuleOutput, waitForRuleSuccessOrStatus, waitForSignalsToBePresent, + waitForAlertToComplete, getRuleForSignalTesting, getRuleForSignalTestingWithTimestampOverride, } from '../../utils'; import { ROLES } from '../../../../plugins/security_solution/common/test'; import { createUserAndRole, deleteUserAndRole } from '../roles_users_utils'; +import { RuleStatusResponse } from '../../../../plugins/security_solution/server/lib/detection_engine/rules/types'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { @@ -287,6 +289,36 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllAlerts(supertest); await esArchiver.unload('security_solution/timestamp_override'); }); + + it('should create a single rule which has a timestamp override for an index pattern that does not exist and write a warning status', async () => { + // defaults to event.ingested timestamp override. + // event.ingested is one of the timestamp fields set on the es archive data + // inside of x-pack/test/functional/es_archives/security_solution/timestamp_override/data.json.gz + const simpleRule = getRuleForSignalTestingWithTimestampOverride(['myfakeindex-1']); + const { body } = await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(simpleRule) + .expect(200); + const bodyId = body.id; + + await waitForAlertToComplete(supertest, bodyId); + await waitForRuleSuccessOrStatus(supertest, bodyId, 'warning'); + + const { body: statusBody } = await supertest + .post(DETECTION_ENGINE_RULES_STATUS_URL) + .set('kbn-xsrf', 'true') + .send({ ids: [bodyId] }) + .expect(200); + + expect((statusBody as RuleStatusResponse)[bodyId].current_status?.status).to.eql('warning'); + expect( + (statusBody as RuleStatusResponse)[bodyId].current_status?.last_success_message + ).to.eql( + 'The following indices are missing the timestamp override field "event.ingested": ["myfakeindex-1"]' + ); + }); + it('should create a single rule which has a timestamp override and generates two signals with a "warning" status', async () => { // defaults to event.ingested timestamp override. // event.ingested is one of the timestamp fields set on the es archive data diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 4875e837556fcc..684cbb6368ad90 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -964,6 +964,19 @@ export const getRule = async ( return body; }; +export const waitForAlertToComplete = async ( + supertest: SuperTest, + id: string +): Promise => { + await waitFor(async () => { + const { body: alertBody } = await supertest + .get(`/api/alerts/alert/${id}/state`) + .set('kbn-xsrf', 'true') + .expect(200); + return alertBody.previousStartedAt != null; + }, 'waitForAlertToComplete'); +}; + /** * Waits for the rule in find status to be 'succeeded' * or the provided status, before continuing From 4ef4b0511b7bb074bafb8ce930b1b8442d1c2b28 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Wed, 17 Feb 2021 17:08:53 -0500 Subject: [PATCH 13/93] [Security Solution] [Detections] Update error banner when refreshing rule status on rule details page (#91051) * refresh error banner on rule details page when refreshing status * remove unused test --- .../rules/rule_status/index.test.tsx | 19 --- .../components/rules/rule_status/index.tsx | 110 +++--------------- .../detection_engine/rules/details/index.tsx | 76 ++++++++++-- 3 files changed, 83 insertions(+), 122 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.test.tsx deleted file mode 100644 index f3669e128ac6ed..00000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; - -import { RuleStatus } from './index'; - -describe('RuleStatus', () => { - it('renders loader correctly', () => { - const wrapper = shallow(); - - expect(wrapper.dive().find('[data-test-subj="rule-status-loader"]')).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx index 677e6de0ff485d..42a6cb8bed1d7d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx @@ -5,115 +5,43 @@ * 2.0. */ -import { - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiHealth, - EuiLoadingSpinner, - EuiText, -} from '@elastic/eui'; -import React, { memo, useCallback, useEffect, useState } from 'react'; -import deepEqual from 'fast-deep-equal'; +import { EuiFlexItem, EuiHealth, EuiText } from '@elastic/eui'; +import React, { memo } from 'react'; -import { - useRuleStatus, - RuleInfoStatus, - RuleStatusType, -} from '../../../containers/detection_engine/rules'; +import { RuleStatusType } from '../../../containers/detection_engine/rules'; import { FormattedDate } from '../../../../common/components/formatted_date'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { getStatusColor } from './helpers'; import * as i18n from './translations'; interface RuleStatusProps { - ruleId: string | null; - ruleEnabled?: boolean | null; + children: React.ReactNode | null | undefined; + statusDate: string | null | undefined; + status: RuleStatusType | null | undefined; } -const RuleStatusComponent: React.FC = ({ ruleId, ruleEnabled }) => { - const [loading, ruleStatus, fetchRuleStatus] = useRuleStatus(ruleId); - const [myRuleEnabled, setMyRuleEnabled] = useState(ruleEnabled ?? null); - const [currentStatus, setCurrentStatus] = useState( - ruleStatus?.current_status ?? null - ); - - useEffect(() => { - if (myRuleEnabled !== ruleEnabled && fetchRuleStatus != null && ruleId != null) { - fetchRuleStatus(ruleId); - if (myRuleEnabled !== ruleEnabled) { - setMyRuleEnabled(ruleEnabled ?? null); - } - } - }, [fetchRuleStatus, myRuleEnabled, ruleId, ruleEnabled, setMyRuleEnabled]); - - useEffect(() => { - if (!deepEqual(currentStatus, ruleStatus?.current_status)) { - setCurrentStatus(ruleStatus?.current_status ?? null); - } - }, [currentStatus, ruleStatus, setCurrentStatus]); - - const handleRefresh = useCallback(() => { - if (fetchRuleStatus != null && ruleId != null) { - fetchRuleStatus(ruleId); - } - }, [fetchRuleStatus, ruleId]); - - const getStatus = useCallback((status: RuleStatusType | null | undefined) => { - if (status == null) { - return getEmptyTagValue(); - } else if (status != null && status === 'partial failure') { - // Temporary fix if on upgrade a rule has a status of 'partial failure' we want to display that text as 'warning' - // On the next subsequent rule run, that 'partial failure' status will be re-written as a 'warning' status - // and this code will no longer be necessary - // TODO: remove this code in 8.0.0 - return 'warning'; - } - return status; - }, []); - +const RuleStatusComponent: React.FC = ({ children, statusDate, status }) => { return ( - + <> - {i18n.STATUS} - {':'} + + + {status ?? getEmptyTagValue()} + + - {loading && ( - - - - )} - {!loading && ( + {statusDate != null && status != null && ( <> - - - {getStatus(currentStatus?.status)} - - + <>{i18n.STATUS_AT} - {currentStatus?.status_date != null && currentStatus?.status != null && ( - <> - - <>{i18n.STATUS_AT} - - - - - - )} - - + + )} - + {children} + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index c4dc9b62c74cd5..4e225917f076df 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -9,6 +9,7 @@ // TODO: Disabling complexity is temporary till this component is refactored as part of lists UI integration import { + EuiButtonIcon, EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem, @@ -24,6 +25,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useParams, useHistory } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; +import deepEqual from 'fast-deep-equal'; import { useDeepEqualSelector, @@ -41,7 +43,7 @@ import { } from '../../../../../common/components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../../../../common/components/search_bar'; import { WrapperPage } from '../../../../../common/components/wrapper_page'; -import { Rule } from '../../../../containers/detection_engine/rules'; +import { Rule, useRuleStatus, RuleInfoStatus } from '../../../../containers/detection_engine/rules'; import { useListsConfig } from '../../../../containers/detection_engine/lists/use_lists_config'; import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_about_rule_details'; @@ -101,6 +103,7 @@ import { import * as detectionI18n from '../../translations'; import * as ruleI18n from '../translations'; +import * as statusI18n from '../../../../components/rules/rule_status/translations'; import * as i18n from './translations'; import { isTab } from '../../../../../common/components/accessibility/helpers'; import { NeedAdminForUpdateRulesCallOut } from '../../../../components/callouts/need_admin_for_update_callout'; @@ -179,6 +182,15 @@ const RuleDetailsPageComponent = () => { const loading = userInfoLoading || listsConfigLoading; const { detailName: ruleId } = useParams<{ detailName: string }>(); const { rule: maybeRule, refresh: refreshRule, loading: ruleLoading } = useRuleAsync(ruleId); + const [loadingStatus, ruleStatus, fetchRuleStatus] = useRuleStatus(ruleId); + const [currentStatus, setCurrentStatus] = useState( + ruleStatus?.current_status ?? null + ); + useEffect(() => { + if (!deepEqual(currentStatus, ruleStatus?.current_status)) { + setCurrentStatus(ruleStatus?.current_status ?? null); + } + }, [currentStatus, ruleStatus, setCurrentStatus]); const [rule, setRule] = useState(null); const isLoading = ruleLoading && rule == null; // This is used to re-trigger api rule status when user de/activate rule @@ -302,33 +314,65 @@ const RuleDetailsPageComponent = () => { ), [ruleDetailTabs, ruleDetailTab, setRuleDetailTab] ); + + const handleRefresh = useCallback(() => { + if (fetchRuleStatus != null && ruleId != null) { + fetchRuleStatus(ruleId); + } + }, [fetchRuleStatus, ruleId]); + + const ruleStatusInfo = useMemo(() => { + return loadingStatus ? ( + + + + ) : ( + <> + + + + + ); + }, [currentStatus, loadingStatus, handleRefresh]); const ruleError = useMemo(() => { - if ( - rule?.status === 'failed' && + if (loadingStatus) { + return ( + + + + ); + } else if ( + currentStatus?.status === 'failed' && ruleDetailTab === RuleDetailTabs.alerts && - rule?.last_failure_at != null + currentStatus?.last_failure_at != null ) { return ( ); } else if ( - (rule?.status === 'warning' || rule?.status === 'partial failure') && + (currentStatus?.status === 'warning' || currentStatus?.status === 'partial failure') && ruleDetailTab === RuleDetailTabs.alerts && - rule?.last_success_at != null + currentStatus?.last_success_at != null ) { return ( ); } return null; - }, [rule, ruleDetailTab]); + }, [ruleDetailTab, currentStatus, loadingStatus]); const updateDateRangeCallback = useCallback( ({ x }) => { @@ -500,7 +544,15 @@ const RuleDetailsPageComponent = () => { , ] : []), - , + <> + + + {statusI18n.STATUS} + {':'} + + {ruleStatusInfo} + + , ]} title={title} > From 9ca41438f1375e257687c027a406f7747b5e2a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Wed, 17 Feb 2021 23:15:16 +0100 Subject: [PATCH 14/93] Update backport to 5.6.6 (#91703) --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index a7959f047ab5c0..96db1977237d1c 100644 --- a/package.json +++ b/package.json @@ -592,7 +592,7 @@ "babel-plugin-require-context-hook": "^1.0.0", "babel-plugin-styled-components": "^1.10.7", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", - "backport": "^5.6.4", + "backport": "^5.6.6", "base64-js": "^1.3.1", "base64url": "^3.0.1", "broadcast-channel": "^3.0.3", diff --git a/yarn.lock b/yarn.lock index 6cb2a6864eb752..836c067c73324a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9020,10 +9020,10 @@ bach@^1.0.0: async-settle "^1.0.0" now-and-later "^2.0.0" -backport@^5.6.4: - version "5.6.4" - resolved "https://registry.yarnpkg.com/backport/-/backport-5.6.4.tgz#8cf4bc750b26d27161306858ee9069218ad7cdfd" - integrity sha512-ZhuZcGxOBHBXFBCwweVf02b+KhWe0tdgg71jPSl583YYxlru+JBRH+TFM8S0J6/6YUuTWO81M9funjehJ18jqg== +backport@^5.6.6: + version "5.6.6" + resolved "https://registry.yarnpkg.com/backport/-/backport-5.6.6.tgz#cb03f948a36386734fa491343b93f4ca280e00f3" + integrity sha512-8O7z0q6m5DfQgrhLDUOLcH2y/rXwSgqv5WIWSSoZpPyRdPpmd3xApIfohlJ3oBX9W0TdbO3aKzaV00qg3H9P7w== dependencies: "@octokit/rest" "^18.0.12" "@types/lodash.difference" "^4.5.6" @@ -9040,7 +9040,7 @@ backport@^5.6.4: lodash.isstring "^4.0.1" lodash.uniq "^4.5.0" make-dir "^3.1.0" - ora "^5.2.0" + ora "^5.3.0" safe-json-stringify "^1.2.0" strip-json-comments "^3.1.1" winston "^3.3.3" @@ -22186,10 +22186,10 @@ ora@^4.0.3, ora@^4.0.4: strip-ansi "^6.0.0" wcwidth "^1.0.1" -ora@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.2.0.tgz#de10bfd2d15514384af45f3fa9d9b1aaf344fda1" - integrity sha512-+wG2v8TUU8EgzPHun1k/n45pXquQ9fHnbXVetl9rRgO6kjZszGGbraF3XPTIdgeA+s1lbRjSEftAnyT0w8ZMvQ== +ora@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.3.0.tgz#fb832899d3a1372fe71c8b2c534bbfe74961bb6f" + integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g== dependencies: bl "^4.0.3" chalk "^4.1.0" From da780f5671b06203c3dc5aeb4026377078a888ec Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Wed, 17 Feb 2021 16:52:48 -0600 Subject: [PATCH 15/93] [Security Solution][Detections] Indicator path followup (#91747) * Remove eslint issue by not permuting our input We instead return a new object from our enrich function. * Fixes editing of non-indicator rules If the user edits a rule without visiting the About tab, they will receive a value of threatIndicatorPath: '' which we'll then try to send to the backend, but it gets rejected. By removing this defaulting logic we get the correct behavior: existing rules default to threatIndicatorPath: undefined, which gets stripped before being sent to the backend. If the rule is an indicator rule, the value will be persisted as expected. * Move default indicator path to common constant --- .../security_solution/common/constants.ts | 5 +++ .../schemas/request/rule_schemas.mock.ts | 3 +- .../schemas/response/rules_schema.mocks.ts | 3 +- .../rules/step_about_rule/default_value.ts | 1 - .../rules/step_about_rule/index.tsx | 3 +- .../detection_engine/rules/helpers.test.tsx | 1 - .../pages/detection_engine/rules/helpers.tsx | 2 +- .../threat_mapping/build_threat_enrichment.ts | 3 +- .../enrich_signal_threat_matches.test.ts | 45 ++++++++++--------- .../enrich_signal_threat_matches.ts | 22 ++++----- 10 files changed, 48 insertions(+), 40 deletions(-) diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 34fcfa5f0befdb..bc71df5d9e0084 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -45,6 +45,11 @@ export const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; // ms export const DEFAULT_RULE_REFRESH_IDLE_VALUE = 2700000; // ms export const DEFAULT_RULE_NOTIFICATION_QUERY_SIZE = 100; +// Document path where threat indicator fields are expected. Used as +// both the source of enrichment fields and the destination for enrichment in +// the generated detection alert +export const DEFAULT_INDICATOR_PATH = 'threat.indicator'; + export enum SecurityPageName { detections = 'detections', overview = 'overview', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts index fb29e37a53fdbe..3bae9551f4df79 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_INDICATOR_PATH } from '../../../constants'; import { MachineLearningCreateSchema, MachineLearningUpdateSchema, @@ -56,7 +57,7 @@ export const getCreateThreatMatchRulesSchemaMock = ( rule_id: ruleId, threat_query: '*:*', threat_index: ['list-index'], - threat_indicator_path: 'threat.indicator', + threat_indicator_path: DEFAULT_INDICATOR_PATH, threat_mapping: [ { entries: [ diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index cf07389e207b34..8dc7427ed09331 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_INDICATOR_PATH } from '../../../constants'; import { getListArrayMock } from '../types/lists.mock'; import { RulesSchema } from './rules_schema'; @@ -150,7 +151,7 @@ export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial = ({ euiFieldProps: { fullWidth: true, disabled: isLoading, - placeholder: 'threat.indicator', + placeholder: DEFAULT_INDICATOR_PATH, }, }} /> diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx index 111eb8a5594a8d..f0511602bd67f7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx @@ -116,7 +116,6 @@ describe('rule helpers', () => { severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, tags: ['tag1', 'tag2'], threat: getThreatMock(), - threatIndicatorPath: '', timestampOverride: 'event.ingested', }; const scheduleRuleStepData = { from: '0s', interval: '5m' }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index d37c2d9141f5d4..c862d484b282b3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -180,7 +180,7 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu }, falsePositives, threat: threat as Threats, - threatIndicatorPath: threatIndicatorPath ?? '', + threatIndicatorPath, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts index 4f38f2db9230a9..cdbafe692c6305 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts @@ -5,13 +5,12 @@ * 2.0. */ +import { DEFAULT_INDICATOR_PATH } from '../../../../../common/constants'; import { SignalSearchResponse, SignalsEnrichment } from '../types'; import { enrichSignalThreatMatches } from './enrich_signal_threat_matches'; import { getThreatList } from './get_threat_list'; import { BuildThreatEnrichmentOptions, GetMatchedThreats } from './types'; -const DEFAULT_INDICATOR_PATH = 'threat.indicator'; - export const buildThreatEnrichment = ({ buildRuleMessage, exceptionItems, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts index fada3141168711..f98f0c88a2dfae 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts @@ -6,6 +6,7 @@ */ import { get } from 'lodash'; +import { DEFAULT_INDICATOR_PATH } from '../../../../../common/constants'; import { getThreatListItemMock } from './build_threat_mapping_filter.mock'; import { @@ -93,7 +94,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries: [], threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toEqual([]); @@ -103,7 +104,7 @@ describe('buildMatchedIndicator', () => { const [indicator] = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(get(indicator, 'matched.atomic')).toEqual('domain_1'); @@ -113,7 +114,7 @@ describe('buildMatchedIndicator', () => { const [indicator] = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(get(indicator, 'matched.field')).toEqual('event.field'); @@ -123,7 +124,7 @@ describe('buildMatchedIndicator', () => { const [indicator] = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(get(indicator, 'matched.type')).toEqual('type_1'); @@ -152,7 +153,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toHaveLength(queries.length); @@ -162,7 +163,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toEqual([ @@ -227,7 +228,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toEqual([ @@ -252,7 +253,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toEqual([ @@ -284,7 +285,7 @@ describe('buildMatchedIndicator', () => { const indicators = buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }); expect(indicators).toEqual([ @@ -316,7 +317,7 @@ describe('buildMatchedIndicator', () => { buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }) ).toThrowError('Expected indicator field to be an object, but found: not an object'); }); @@ -337,7 +338,7 @@ describe('buildMatchedIndicator', () => { buildMatchedIndicator({ queries, threats, - indicatorPath: 'threat.indicator', + indicatorPath: DEFAULT_INDICATOR_PATH, }) ).toThrowError('Expected indicator field to be an object, but found: not an object'); }); @@ -366,7 +367,7 @@ describe('enrichSignalThreatMatches', () => { const enrichedSignals = await enrichSignalThreatMatches( signals, getMatchedThreats, - 'threat.indicator' + DEFAULT_INDICATOR_PATH ); expect(enrichedSignals.hits.hits).toEqual([]); @@ -381,10 +382,10 @@ describe('enrichSignalThreatMatches', () => { const enrichedSignals = await enrichSignalThreatMatches( signals, getMatchedThreats, - 'threat.indicator' + DEFAULT_INDICATOR_PATH ); const [enrichedHit] = enrichedSignals.hits.hits; - const indicators = get(enrichedHit._source, 'threat.indicator'); + const indicators = get(enrichedHit._source, DEFAULT_INDICATOR_PATH); expect(indicators).toEqual([ { existing: 'indicator' }, @@ -406,10 +407,10 @@ describe('enrichSignalThreatMatches', () => { const enrichedSignals = await enrichSignalThreatMatches( signals, getMatchedThreats, - 'threat.indicator' + DEFAULT_INDICATOR_PATH ); const [enrichedHit] = enrichedSignals.hits.hits; - const indicators = get(enrichedHit._source, 'threat.indicator'); + const indicators = get(enrichedHit._source, DEFAULT_INDICATOR_PATH); expect(indicators).toEqual([ { @@ -427,10 +428,10 @@ describe('enrichSignalThreatMatches', () => { const enrichedSignals = await enrichSignalThreatMatches( signals, getMatchedThreats, - 'threat.indicator' + DEFAULT_INDICATOR_PATH ); const [enrichedHit] = enrichedSignals.hits.hits; - const indicators = get(enrichedHit._source, 'threat.indicator'); + const indicators = get(enrichedHit._source, DEFAULT_INDICATOR_PATH); expect(indicators).toEqual([ { existing: 'indicator' }, @@ -450,7 +451,7 @@ describe('enrichSignalThreatMatches', () => { }); const signals = getSignalsResponseMock([signalHit]); await expect(() => - enrichSignalThreatMatches(signals, getMatchedThreats, 'threat.indicator') + enrichSignalThreatMatches(signals, getMatchedThreats, DEFAULT_INDICATOR_PATH) ).rejects.toThrowError('Expected threat field to be an object, but found: whoops'); }); @@ -486,7 +487,7 @@ describe('enrichSignalThreatMatches', () => { 'custom_threat.custom_indicator' ); const [enrichedHit] = enrichedSignals.hits.hits; - const indicators = get(enrichedHit._source, 'threat.indicator'); + const indicators = get(enrichedHit._source, DEFAULT_INDICATOR_PATH); expect(indicators).toEqual([ { @@ -529,13 +530,13 @@ describe('enrichSignalThreatMatches', () => { const enrichedSignals = await enrichSignalThreatMatches( signals, getMatchedThreats, - 'threat.indicator' + DEFAULT_INDICATOR_PATH ); expect(enrichedSignals.hits.total).toEqual(expect.objectContaining({ value: 1 })); expect(enrichedSignals.hits.hits).toHaveLength(1); const [enrichedHit] = enrichedSignals.hits.hits; - const indicators = get(enrichedHit._source, 'threat.indicator'); + const indicators = get(enrichedHit._source, DEFAULT_INDICATOR_PATH); expect(indicators).toEqual([ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts index c5b032207f1c5a..761a58224fac58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts @@ -6,6 +6,7 @@ */ import { get, isObject } from 'lodash'; +import { DEFAULT_INDICATOR_PATH } from '../../../../../common/constants'; import type { SignalSearchResponse, SignalSourceHit } from '../types'; import type { @@ -91,7 +92,7 @@ export const enrichSignalThreatMatches = async ( if (!isObject(threat)) { throw new Error(`Expected threat field to be an object, but found: ${threat}`); } - const existingIndicatorValue = get(signalHit._source, 'threat.indicator') ?? []; + const existingIndicatorValue = get(signalHit._source, DEFAULT_INDICATOR_PATH) ?? []; const existingIndicators = [existingIndicatorValue].flat(); // ensure indicators is an array return { @@ -105,14 +106,15 @@ export const enrichSignalThreatMatches = async ( }, }; }); - /* eslint-disable require-atomic-updates */ - signals.hits.hits = enrichedSignals; - if (isObject(signals.hits.total)) { - signals.hits.total.value = enrichedSignals.length; - } else { - signals.hits.total = enrichedSignals.length; - } - /* eslint-enable require-atomic-updates */ - return signals; + return { + ...signals, + hits: { + ...signals.hits, + hits: enrichedSignals, + total: isObject(signals.hits.total) + ? { ...signals.hits.total, value: enrichedSignals.length } + : enrichedSignals.length, + }, + }; }; From 31810c65566ce58acef283062d3f286444cd3da8 Mon Sep 17 00:00:00 2001 From: igoristic Date: Wed, 17 Feb 2021 18:09:58 -0500 Subject: [PATCH 16/93] Added flag to change the alerts app context requirment (#91726) --- x-pack/plugins/monitoring/common/constants.ts | 5 +++++ .../public/alerts/ccr_read_exceptions_alert/index.tsx | 8 ++++++-- .../public/alerts/cpu_usage_alert/cpu_usage_alert.tsx | 8 ++++++-- .../monitoring/public/alerts/disk_usage_alert/index.tsx | 8 ++++++-- .../public/alerts/large_shard_size_alert/index.tsx | 8 ++++++-- .../public/alerts/legacy_alert/legacy_alert.tsx | 8 ++++++-- .../monitoring/public/alerts/memory_usage_alert/index.tsx | 8 ++++++-- .../missing_monitoring_data_alert.tsx | 8 ++++++-- .../public/alerts/thread_pool_rejections_alert/index.tsx | 3 ++- 9 files changed, 49 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 8b1e91e2caf67e..18c96106566486 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -580,6 +580,11 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email'; */ export const ALERT_ACTION_TYPE_LOG = '.server-log'; +/** + * To enable modifing of alerts in under actions + */ +export const ALERT_REQUIRES_APP_CONTEXT = false; + export const ALERT_EMAIL_SERVICES = ['gmail', 'hotmail', 'icloud', 'outlook365', 'ses', 'yahoo']; /** diff --git a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx index f0b58b41015052..3088e2a75c3492 100644 --- a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx @@ -9,7 +9,11 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { Expression, Props } from '../components/param_details_form/expression'; import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; -import { ALERT_CCR_READ_EXCEPTIONS, ALERT_DETAILS } from '../../../common/constants'; +import { + ALERT_CCR_READ_EXCEPTIONS, + ALERT_DETAILS, + ALERT_REQUIRES_APP_CONTEXT, +} from '../../../common/constants'; import { AlertTypeParams } from '../../../../alerts/common'; interface ValidateOptions extends AlertTypeParams { @@ -45,6 +49,6 @@ export function createCCRReadExceptionsAlertType(): AlertTypeModel { return { @@ -26,6 +30,6 @@ export function createDiskUsageAlertType(): AlertTypeModel ), validate, defaultActionMessage: '{{context.internalFullMessage}}', - requiresAppContext: true, + requiresAppContext: ALERT_REQUIRES_APP_CONTEXT, }; } diff --git a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx index 3f23035f526496..87850f893797a2 100644 --- a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx @@ -10,7 +10,11 @@ import { i18n } from '@kbn/i18n'; import { EuiTextColor, EuiSpacer } from '@elastic/eui'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; -import { LEGACY_ALERTS, LEGACY_ALERT_DETAILS } from '../../../common/constants'; +import { + LEGACY_ALERTS, + LEGACY_ALERT_DETAILS, + ALERT_REQUIRES_APP_CONTEXT, +} from '../../../common/constants'; export function createLegacyAlertTypes(): AlertTypeModel[] { return LEGACY_ALERTS.map((legacyAlert) => { @@ -34,7 +38,7 @@ export function createLegacyAlertTypes(): AlertTypeModel[] { ), defaultActionMessage: '{{context.internalFullMessage}}', validate: () => ({ errors: {} }), - requiresAppContext: true, + requiresAppContext: ALERT_REQUIRES_APP_CONTEXT, }; }); } diff --git a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx index 8437faf2258b97..6639b4f2f6a489 100644 --- a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx @@ -11,7 +11,11 @@ import { Expression, Props } from '../components/param_details_form/expression'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; -import { ALERT_MEMORY_USAGE, ALERT_DETAILS } from '../../../common/constants'; +import { + ALERT_MEMORY_USAGE, + ALERT_DETAILS, + ALERT_REQUIRES_APP_CONTEXT, +} from '../../../common/constants'; export function createMemoryUsageAlertType(): AlertTypeModel { return { @@ -26,6 +30,6 @@ export function createMemoryUsageAlertType(): AlertTypeModel Date: Thu, 18 Feb 2021 01:37:52 +0100 Subject: [PATCH 17/93] [Security Solution][Detections] Pre-refactoring for the rule management table (#91302) **Related to:** https://github.com/elastic/kibana/pull/89877 ## Summary This is based on https://github.com/elastic/kibana/pull/89877 and the kind of pre-refactoring that has been done there. Mainly this: - consolidates application logic in a single place (moves the reducer and the side effects close to each other, etc) - removes some of the redundant state, leverages the reducer as the source of truth for state - makes it easier to dispatch events, removes some of the noise While this refactoring is a totally unfinished work, and might look not good enough (or at all), still I'd like to merge it because of the logic consolidation. I'm going to finalize the refactoring later when I start implementing new filters and other table UX improvements. So the code is going to become better and maybe even quite different from what it is right now. (Btw because of that, I'm not adding or removing any tests here because this is an intermediate kind of state of the code). ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../components/rules/rule_switch/index.tsx | 5 +- .../detection_engine/rules/index.ts | 2 +- .../rules/rules_table/index.ts | 11 ++ .../rules/rules_table/rules_table_facade.ts | 84 +++++++++ .../rules_table/rules_table_reducer.test.ts} | 31 ++-- .../rules/rules_table/rules_table_reducer.ts | 165 ++++++++++++++++++ .../{ => rules_table}/use_rules.test.tsx | 4 +- .../rules/{ => rules_table}/use_rules.tsx | 8 +- .../rules/rules_table/use_rules_table.ts | 131 ++++++++++++++ .../detection_engine/rules/all/actions.tsx | 13 +- .../rules/all/batch_actions.tsx | 4 +- .../detection_engine/rules/all/columns.tsx | 6 +- .../detection_engine/rules/all/index.test.tsx | 52 ++++-- .../detection_engine/rules/all/reducer.ts | 156 ----------------- .../rules/all/rules_tables.tsx | 151 ++++++---------- 15 files changed, 522 insertions(+), 301 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/index.ts create mode 100644 x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_facade.ts rename x-pack/plugins/security_solution/public/detections/{pages/detection_engine/rules/all/reducer.test.ts => containers/detection_engine/rules/rules_table/rules_table_reducer.test.ts} (94%) create mode 100644 x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.ts rename x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/{ => rules_table}/use_rules.test.tsx (99%) rename x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/{ => rules_table}/use_rules.tsx (93%) create mode 100644 x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx index 4f87f9332475b1..268ffe620ad4ea 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx @@ -17,9 +17,8 @@ import styled from 'styled-components'; import React, { useMemo, useCallback, useState, useEffect } from 'react'; import * as i18n from '../../../pages/detection_engine/rules/translations'; -import { enableRules } from '../../../containers/detection_engine/rules'; +import { enableRules, RulesTableAction } from '../../../containers/detection_engine/rules'; import { enableRulesAction } from '../../../pages/detection_engine/rules/all/actions'; -import { Action } from '../../../pages/detection_engine/rules/all/reducer'; import { useStateToaster, displayErrorToast } from '../../../../common/components/toasters'; import { bucketRulesResponse } from '../../../pages/detection_engine/rules/all/helpers'; @@ -33,7 +32,7 @@ const StaticSwitch = styled(EuiSwitch)` StaticSwitch.displayName = 'StaticSwitch'; export interface RuleSwitchProps { - dispatch?: React.Dispatch; + dispatch?: React.Dispatch; id: string; enabled: boolean; isDisabled?: boolean; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts index 751cde64bb87d6..8128eb045f759d 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts @@ -10,6 +10,6 @@ export * from './use_update_rule'; export * from './use_create_rule'; export * from './types'; export * from './use_rule'; -export * from './use_rules'; +export * from './rules_table'; export * from './use_pre_packaged_rules'; export * from './use_rule_status'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/index.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/index.ts new file mode 100644 index 00000000000000..a05349fa4fa3a8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './rules_table_facade'; +export * from './rules_table_reducer'; +export * from './use_rules'; +export * from './use_rules_table'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_facade.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_facade.ts new file mode 100644 index 00000000000000..77c327c9f7939b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_facade.ts @@ -0,0 +1,84 @@ +/* + * 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 { Dispatch } from 'react'; +import { Rule, FilterOptions, PaginationOptions } from '../types'; +import { RulesTableAction, LoadingRuleAction } from './rules_table_reducer'; + +export interface RulesTableFacade { + setRules(newRules: Rule[], newPagination: Partial): void; + updateRules(rules: Rule[]): void; + updateOptions(filter: Partial, pagination: Partial): void; + actionStarted(actionType: LoadingRuleAction, ruleIds: string[]): void; + actionStopped(): void; + setShowIdleModal(show: boolean): void; + setLastRefreshDate(): void; + setAutoRefreshOn(on: boolean): void; +} + +export const createRulesTableFacade = (dispatch: Dispatch): RulesTableFacade => { + return { + setRules: (newRules: Rule[], newPagination: Partial) => { + dispatch({ + type: 'setRules', + rules: newRules, + pagination: newPagination, + }); + }, + + updateRules: (rules: Rule[]) => { + dispatch({ + type: 'updateRules', + rules, + }); + }, + + updateOptions: (filter: Partial, pagination: Partial) => { + dispatch({ + type: 'updateFilterOptions', + filterOptions: filter, + pagination, + }); + }, + + actionStarted: (actionType: LoadingRuleAction, ruleIds: string[]) => { + dispatch({ + type: 'loadingRuleIds', + actionType, + ids: ruleIds, + }); + }, + + actionStopped: () => { + dispatch({ + type: 'loadingRuleIds', + actionType: null, + ids: [], + }); + }, + + setShowIdleModal: (show: boolean) => { + dispatch({ + type: 'setShowIdleModal', + show, + }); + }, + + setLastRefreshDate: () => { + dispatch({ + type: 'setLastRefreshDate', + }); + }, + + setAutoRefreshOn: (on: boolean) => { + dispatch({ + type: 'setAutoRefreshOn', + on, + }); + }, + }; +}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.test.ts similarity index 94% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.test.ts rename to x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.test.ts index 20e4e5f7473492..1a45c60dba58a4 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.test.ts @@ -5,13 +5,17 @@ * 2.0. */ -import { FilterOptions, PaginationOptions } from '../../../../containers/detection_engine/rules'; +import { mockRule } from '../../../../pages/detection_engine/rules/all/__mocks__/mock'; +import { FilterOptions, PaginationOptions } from '../types'; +import { RulesTableAction, RulesTableState, createRulesTableReducer } from './rules_table_reducer'; -import { Action, State, allRulesReducer } from './reducer'; -import { mockRule } from './__mocks__/mock'; - -const initialState: State = { - exportRuleIds: [], +const initialState: RulesTableState = { + rules: [], + pagination: { + page: 1, + perPage: 20, + total: 0, + }, filterOptions: { filter: '', sortField: 'enabled', @@ -20,29 +24,24 @@ const initialState: State = { showCustomRules: false, showElasticRules: false, }, - loadingRuleIds: [], loadingRulesAction: null, - pagination: { - page: 1, - perPage: 20, - total: 0, - }, - rules: [], + loadingRuleIds: [], selectedRuleIds: [], + exportRuleIds: [], lastUpdated: 0, - showIdleModal: false, isRefreshOn: false, + showIdleModal: false, }; describe('allRulesReducer', () => { - let reducer: (state: State, action: Action) => State; + let reducer: (state: RulesTableState, action: RulesTableAction) => RulesTableState; beforeEach(() => { jest.useFakeTimers(); jest .spyOn(global.Date, 'now') .mockImplementationOnce(() => new Date('2020-10-31T11:01:58.135Z').valueOf()); - reducer = allRulesReducer({ current: undefined }); + reducer = createRulesTableReducer({ current: undefined }); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.ts new file mode 100644 index 00000000000000..edcf4f6395d895 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.ts @@ -0,0 +1,165 @@ +/* + * 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 React from 'react'; +import { EuiBasicTable } from '@elastic/eui'; +import { FilterOptions, PaginationOptions, Rule } from '../types'; + +export type LoadingRuleAction = + | 'load' + | 'duplicate' + | 'enable' + | 'disable' + | 'export' + | 'delete' + | null; + +export interface RulesTableState { + rules: Rule[]; + pagination: PaginationOptions; + filterOptions: FilterOptions; + loadingRulesAction: LoadingRuleAction; + loadingRuleIds: string[]; + selectedRuleIds: string[]; + exportRuleIds: string[]; + lastUpdated: number; + isRefreshOn: boolean; + showIdleModal: boolean; +} + +export type RulesTableAction = + | { type: 'setRules'; rules: Rule[]; pagination: Partial } + | { type: 'updateRules'; rules: Rule[] } + | { + type: 'updateFilterOptions'; + filterOptions: Partial; + pagination: Partial; + } + | { type: 'loadingRuleIds'; ids: string[]; actionType: LoadingRuleAction } + | { type: 'selectedRuleIds'; ids: string[] } + | { type: 'exportRuleIds'; ids: string[] } + | { type: 'setLastRefreshDate' } + | { type: 'setAutoRefreshOn'; on: boolean } + | { type: 'setShowIdleModal'; show: boolean } + | { type: 'failure' }; + +export const createRulesTableReducer = ( + tableRef: React.MutableRefObject | undefined> +) => { + const rulesTableReducer = (state: RulesTableState, action: RulesTableAction): RulesTableState => { + switch (action.type) { + case 'setRules': { + if ( + tableRef != null && + tableRef.current != null && + tableRef.current.changeSelection != null + ) { + // for future devs: eui basic table is not giving us a prop to set the value, so + // we are using the ref in setTimeout to reset on the next loop so that we + // do not get a warning telling us we are trying to update during a render + window.setTimeout(() => tableRef?.current?.changeSelection([]), 0); + } + + return { + ...state, + rules: action.rules, + selectedRuleIds: [], + loadingRuleIds: [], + loadingRulesAction: null, + pagination: { + ...state.pagination, + ...action.pagination, + }, + }; + } + case 'updateRules': { + const ruleIds = state.rules.map((r) => r.id); + const updatedRules = action.rules.reduce((rules, updatedRule) => { + let newRules = rules; + if (ruleIds.includes(updatedRule.id)) { + newRules = newRules.map((r) => (updatedRule.id === r.id ? updatedRule : r)); + } else { + newRules = [...newRules, updatedRule]; + } + return newRules; + }, state.rules); + const updatedRuleIds = action.rules.map((r) => r.id); + const newLoadingRuleIds = state.loadingRuleIds.filter((id) => !updatedRuleIds.includes(id)); + return { + ...state, + rules: updatedRules, + loadingRuleIds: newLoadingRuleIds, + loadingRulesAction: newLoadingRuleIds.length === 0 ? null : state.loadingRulesAction, + }; + } + case 'updateFilterOptions': { + return { + ...state, + filterOptions: { + ...state.filterOptions, + ...action.filterOptions, + }, + pagination: { + ...state.pagination, + ...action.pagination, + }, + }; + } + case 'loadingRuleIds': { + return { + ...state, + loadingRuleIds: action.actionType == null ? [] : [...state.loadingRuleIds, ...action.ids], + loadingRulesAction: action.actionType, + }; + } + case 'selectedRuleIds': { + return { + ...state, + selectedRuleIds: action.ids, + }; + } + case 'exportRuleIds': { + return { + ...state, + loadingRuleIds: action.ids, + loadingRulesAction: 'export', + exportRuleIds: action.ids, + }; + } + case 'setLastRefreshDate': { + return { + ...state, + lastUpdated: Date.now(), + }; + } + case 'setAutoRefreshOn': { + return { + ...state, + isRefreshOn: action.on, + }; + } + case 'setShowIdleModal': { + return { + ...state, + showIdleModal: action.show, + isRefreshOn: !action.show, + }; + } + case 'failure': { + return { + ...state, + rules: [], + }; + } + default: { + return state; + } + } + }; + + return rulesTableReducer; +}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.test.tsx rename to x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx index 5d226e8152596e..4532d3427375b1 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx @@ -7,9 +7,9 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useRules, UseRules, ReturnRules } from './use_rules'; -import * as api from './api'; +import * as api from '../api'; -jest.mock('./api'); +jest.mock('../api'); describe('useRules', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.tsx rename to x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx index 02784333992be2..f3c90ae12ae33c 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rules.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx @@ -7,10 +7,10 @@ import { useEffect, useState, useRef } from 'react'; -import { FetchRulesResponse, FilterOptions, PaginationOptions, Rule } from './types'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; -import { fetchRules } from './api'; -import * as i18n from './translations'; +import { FetchRulesResponse, FilterOptions, PaginationOptions, Rule } from '../types'; +import { errorToToaster, useStateToaster } from '../../../../../common/components/toasters'; +import { fetchRules } from '../api'; +import * as i18n from '../translations'; export type ReturnRules = [boolean, FetchRulesResponse | null, () => Promise]; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts new file mode 100644 index 00000000000000..f31b2894301ba6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts @@ -0,0 +1,131 @@ +/* + * 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 { Dispatch, useMemo, useReducer, useEffect, useRef } from 'react'; +import { EuiBasicTable } from '@elastic/eui'; + +import { errorToToaster, useStateToaster } from '../../../../../common/components/toasters'; +import * as i18n from '../translations'; + +import { fetchRules } from '../api'; +import { createRulesTableReducer, RulesTableState, RulesTableAction } from './rules_table_reducer'; +import { createRulesTableFacade, RulesTableFacade } from './rules_table_facade'; + +const INITIAL_SORT_FIELD = 'enabled'; + +const initialStateDefaults: RulesTableState = { + rules: [], + pagination: { + page: 1, + perPage: 20, + total: 0, + }, + filterOptions: { + filter: '', + sortField: INITIAL_SORT_FIELD, + sortOrder: 'desc', + tags: [], + showCustomRules: false, + showElasticRules: false, + }, + loadingRulesAction: null, + loadingRuleIds: [], + selectedRuleIds: [], + exportRuleIds: [], + lastUpdated: 0, + isRefreshOn: true, + showIdleModal: false, +}; + +export interface UseRulesTableParams { + tableRef: React.MutableRefObject | undefined>; + initialStateOverride?: Partial; +} + +export interface UseRulesTableReturn extends RulesTableFacade { + state: RulesTableState; + dispatch: Dispatch; + reFetchRules: () => Promise; +} + +export const useRulesTable = (params: UseRulesTableParams): UseRulesTableReturn => { + const { tableRef, initialStateOverride } = params; + + const initialState: RulesTableState = { + ...initialStateDefaults, + lastUpdated: Date.now(), + ...initialStateOverride, + }; + + const reducer = useMemo(() => createRulesTableReducer(tableRef), [tableRef]); + const [state, dispatch] = useReducer(reducer, initialState); + const facade = useRef(createRulesTableFacade(dispatch)); + + const reFetchRules = useRef<() => Promise>(() => Promise.resolve()); + const [, dispatchToaster] = useStateToaster(); + + const { pagination, filterOptions } = state; + const filterTags = filterOptions.tags.sort().join(); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + const fetchData = async () => { + try { + facade.current.actionStarted('load', []); + + const fetchRulesResult = await fetchRules({ + filterOptions, + pagination, + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + facade.current.setRules(fetchRulesResult.data, { + page: fetchRulesResult.page, + perPage: fetchRulesResult.perPage, + total: fetchRulesResult.total, + }); + } + } catch (error) { + if (isSubscribed) { + errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + facade.current.setRules([], {}); + } + } + if (isSubscribed) { + facade.current.actionStopped(); + } + }; + + fetchData(); + reFetchRules.current = () => fetchData(); + + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + pagination.page, + pagination.perPage, + filterOptions.filter, + filterOptions.sortField, + filterOptions.sortOrder, + filterTags, + filterOptions.showCustomRules, + filterOptions.showElasticRules, + ]); + + return { + state, + dispatch, + ...facade.current, + reFetchRules: reFetchRules.current, + }; +}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx index 44b0476501a5b9..3b1f9e620127d8 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx @@ -13,6 +13,7 @@ import { duplicateRules, enableRules, Rule, + RulesTableAction, } from '../../../../containers/detection_engine/rules'; import { getEditRuleUrl } from '../../../../../common/components/link_to/redirect_to_detection_engine'; @@ -27,7 +28,6 @@ import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../../../common/lib/t import * as i18n from '../translations'; import { bucketRulesResponse } from './helpers'; -import { Action } from './reducer'; export const editRuleAction = (rule: Rule, history: H.History) => { history.push(getEditRuleUrl(rule.id)); @@ -36,7 +36,7 @@ export const editRuleAction = (rule: Rule, history: H.History) => { export const duplicateRulesAction = async ( rules: Rule[], ruleIds: string[], - dispatch: React.Dispatch, + dispatch: React.Dispatch, dispatchToaster: Dispatch ) => { try { @@ -59,13 +59,16 @@ export const duplicateRulesAction = async ( } }; -export const exportRulesAction = (exportRuleId: string[], dispatch: React.Dispatch) => { +export const exportRulesAction = ( + exportRuleId: string[], + dispatch: React.Dispatch +) => { dispatch({ type: 'exportRuleIds', ids: exportRuleId }); }; export const deleteRulesAction = async ( ruleIds: string[], - dispatch: React.Dispatch, + dispatch: React.Dispatch, dispatchToaster: Dispatch, onRuleDeleted?: () => void ) => { @@ -96,7 +99,7 @@ export const deleteRulesAction = async ( export const enableRulesAction = async ( ids: string[], enabled: boolean, - dispatch: React.Dispatch, + dispatch: React.Dispatch, dispatchToaster: Dispatch ) => { const errorTitle = enabled diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx index ee19a0b5350754..d3e055a695d61b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx @@ -8,7 +8,7 @@ import { EuiContextMenuItem, EuiToolTip } from '@elastic/eui'; import React, { Dispatch } from 'react'; import * as i18n from '../translations'; -import { Action } from './reducer'; +import { RulesTableAction } from '../../../../containers/detection_engine/rules/rules_table'; import { deleteRulesAction, duplicateRulesAction, @@ -23,7 +23,7 @@ import { canEditRuleWithActions } from '../../../../../common/utils/privileges'; interface GetBatchItems { closePopover: () => void; - dispatch: Dispatch; + dispatch: Dispatch; dispatchToaster: Dispatch; hasMlPermissions: boolean; hasActionsPrivileges: boolean; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx index d110f2d52b3c5e..d2488bd3d043cd 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx @@ -34,14 +34,14 @@ import { editRuleAction, exportRulesAction, } from './actions'; -import { Action } from './reducer'; +import { RulesTableAction } from '../../../../containers/detection_engine/rules/rules_table'; import { LocalizedDateTooltip } from '../../../../../common/components/localized_date_tooltip'; import { LinkAnchor } from '../../../../../common/components/links'; import { getToolTipContent, canEditRuleWithActions } from '../../../../../common/utils/privileges'; import { TagsDisplay } from './tag_display'; export const getActions = ( - dispatch: React.Dispatch, + dispatch: React.Dispatch, dispatchToaster: Dispatch, history: H.History, reFetchRules: () => Promise, @@ -112,7 +112,7 @@ export type RulesColumns = EuiBasicTableColumn | EuiTableActionsColumnType export type RulesStatusesColumns = EuiBasicTableColumn; type FormatUrl = (path: string) => string; interface GetColumns { - dispatch: React.Dispatch; + dispatch: React.Dispatch; dispatchToaster: Dispatch; formatUrl: FormatUrl; history: H.History; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx index a784d23ab00153..7f9061b6abc83b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx @@ -13,7 +13,7 @@ import '../../../../../common/mock/match_media'; import '../../../../../common/mock/formatted_relative'; import { AllRules } from './index'; import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; -import { useRules, useRulesStatuses } from '../../../../containers/detection_engine/rules'; +import { useRulesTable, useRulesStatuses } from '../../../../containers/detection_engine/rules'; import { TestProviders } from '../../../../../common/mock'; import { createUseUiSetting$Mock } from '../../../../../common/lib/kibana/kibana_react.mock'; import { @@ -40,6 +40,7 @@ jest.mock('../../../../containers/detection_engine/rules'); const useKibanaMock = useKibana as jest.Mocked; const mockUseUiSetting$ = useUiSetting$ as jest.Mock; +const mockUseRulesTable = useRulesTable as jest.Mock; describe('AllRules', () => { const mockRefetchRulesData = jest.fn(); @@ -62,13 +63,9 @@ describe('AllRules', () => { : useUiSetting$Mock(key, defaultValue); }); - (useRules as jest.Mock).mockReturnValue([ - false, - { - page: 1, - perPage: 20, - total: 1, - data: [ + mockUseRulesTable.mockImplementation(({ initialStateOverride }) => { + const initialState = { + rules: [ { actions: [], created_at: '2020-02-14T19:49:28.178Z', @@ -101,9 +98,42 @@ describe('AllRules', () => { version: 1, }, ], - }, - mockRefetchRulesData, - ]); + pagination: { + page: 1, + perPage: 20, + total: 1, + }, + filterOptions: { + filter: '', + sortField: 'enabled', + sortOrder: 'desc', + tags: [], + showCustomRules: false, + showElasticRules: false, + }, + loadingRulesAction: null, + loadingRuleIds: [], + selectedRuleIds: [], + exportRuleIds: [], + lastUpdated: 0, + isRefreshOn: true, + showIdleModal: false, + }; + + return { + state: { ...initialState, ...initialStateOverride }, + dispatch: jest.fn(), + reFetchRules: mockRefetchRulesData, + setRules: jest.fn(), + updateRules: jest.fn(), + updateOptions: jest.fn(), + actionStarted: jest.fn(), + actionStopped: jest.fn(), + setShowIdleModal: jest.fn(), + setLastRefreshDate: jest.fn(), + setAutoRefreshOn: jest.fn(), + }; + }); (useRulesStatuses as jest.Mock).mockReturnValue({ loading: false, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts deleted file mode 100644 index 60798f10a4c58f..00000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type React from 'react'; -import { EuiBasicTable } from '@elastic/eui'; -import { - FilterOptions, - PaginationOptions, - Rule, -} from '../../../../containers/detection_engine/rules'; - -type LoadingRuleAction = 'duplicate' | 'enable' | 'disable' | 'export' | 'delete' | null; -export interface State { - exportRuleIds: string[]; - filterOptions: FilterOptions; - loadingRuleIds: string[]; - loadingRulesAction: LoadingRuleAction; - pagination: PaginationOptions; - rules: Rule[]; - selectedRuleIds: string[]; - lastUpdated: number; - showIdleModal: boolean; - isRefreshOn: boolean; -} - -export type Action = - | { type: 'exportRuleIds'; ids: string[] } - | { type: 'loadingRuleIds'; ids: string[]; actionType: LoadingRuleAction } - | { type: 'selectedRuleIds'; ids: string[] } - | { type: 'setRules'; rules: Rule[]; pagination: Partial } - | { type: 'updateRules'; rules: Rule[] } - | { - type: 'updateFilterOptions'; - filterOptions: Partial; - pagination: Partial; - } - | { type: 'failure' } - | { type: 'setLastRefreshDate' } - | { type: 'setShowIdleModal'; show: boolean } - | { type: 'setAutoRefreshOn'; on: boolean }; - -export const allRulesReducer = ( - tableRef: React.MutableRefObject | undefined> -) => (state: State, action: Action): State => { - switch (action.type) { - case 'exportRuleIds': { - return { - ...state, - loadingRuleIds: action.ids, - loadingRulesAction: 'export', - exportRuleIds: action.ids, - }; - } - case 'loadingRuleIds': { - return { - ...state, - loadingRuleIds: action.actionType == null ? [] : [...state.loadingRuleIds, ...action.ids], - loadingRulesAction: action.actionType, - }; - } - case 'selectedRuleIds': { - return { - ...state, - selectedRuleIds: action.ids, - }; - } - case 'setRules': { - if ( - tableRef != null && - tableRef.current != null && - tableRef.current.changeSelection != null - ) { - // for future devs: eui basic table is not giving us a prop to set the value, so - // we are using the ref in setTimeout to reset on the next loop so that we - // do not get a warning telling us we are trying to update during a render - window.setTimeout(() => tableRef?.current?.changeSelection([]), 0); - } - - return { - ...state, - rules: action.rules, - selectedRuleIds: [], - loadingRuleIds: [], - loadingRulesAction: null, - pagination: { - ...state.pagination, - ...action.pagination, - }, - }; - } - case 'updateRules': { - const ruleIds = state.rules.map((r) => r.id); - const updatedRules = action.rules.reduce((rules, updatedRule) => { - let newRules = rules; - if (ruleIds.includes(updatedRule.id)) { - newRules = newRules.map((r) => (updatedRule.id === r.id ? updatedRule : r)); - } else { - newRules = [...newRules, updatedRule]; - } - return newRules; - }, state.rules); - const updatedRuleIds = action.rules.map((r) => r.id); - const newLoadingRuleIds = state.loadingRuleIds.filter((id) => !updatedRuleIds.includes(id)); - return { - ...state, - rules: updatedRules, - loadingRuleIds: newLoadingRuleIds, - loadingRulesAction: newLoadingRuleIds.length === 0 ? null : state.loadingRulesAction, - }; - } - case 'updateFilterOptions': { - return { - ...state, - filterOptions: { - ...state.filterOptions, - ...action.filterOptions, - }, - pagination: { - ...state.pagination, - ...action.pagination, - }, - }; - } - case 'failure': { - return { - ...state, - rules: [], - }; - } - case 'setLastRefreshDate': { - return { - ...state, - lastUpdated: Date.now(), - }; - } - case 'setShowIdleModal': { - return { - ...state, - showIdleModal: action.show, - isRefreshOn: !action.show, - }; - } - case 'setAutoRefreshOn': { - return { - ...state, - isRefreshOn: action.on, - }; - } - default: - return state; - } -}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx index 04bf3c544030a4..a40833d8d14acb 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx @@ -12,21 +12,21 @@ import { EuiConfirmModal, EuiWindowEvent, } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import uuid from 'uuid'; import { debounce } from 'lodash/fp'; import { History } from 'history'; import { - useRules, + useRulesTable, useRulesStatuses, CreatePreBuiltRules, FilterOptions, Rule, - PaginationOptions, exportRules, RulesSortingFields, } from '../../../../containers/detection_engine/rules'; + import { FormatUrl } from '../../../../../common/components/link_to'; import { HeaderSection } from '../../../../../common/components/header_section'; import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; @@ -42,7 +42,6 @@ import { EuiBasicTableOnChange } from '../types'; import { getBatchItems } from './batch_actions'; import { getColumns, getMonitoringColumns } from './columns'; import { showRulesTable } from './helpers'; -import { allRulesReducer, State } from './reducer'; import { RulesTableFilters } from './rules_table_filters/rules_table_filters'; import { useMlCapabilities } from '../../../../../common/components/ml/hooks/use_ml_capabilities'; import { hasMlAdminPermissions } from '../../../../../../common/machine_learning/has_ml_admin_permissions'; @@ -54,29 +53,6 @@ import { DEFAULT_RULES_TABLE_REFRESH_SETTING } from '../../../../../../common/co import { AllRulesTabs } from '.'; const INITIAL_SORT_FIELD = 'enabled'; -const initialState: State = { - exportRuleIds: [], - filterOptions: { - filter: '', - sortField: INITIAL_SORT_FIELD, - sortOrder: 'desc', - tags: [], - showCustomRules: false, - showElasticRules: false, - }, - loadingRuleIds: [], - loadingRulesAction: null, - pagination: { - page: 1, - perPage: 20, - total: 0, - }, - rules: [], - selectedRuleIds: [], - lastUpdated: 0, - showIdleModal: false, - isRefreshOn: true, -}; interface RulesTableProps { history: History; @@ -119,7 +95,7 @@ export const RulesTables = React.memo( selectedTab, }) => { const [initLoading, setInitLoading] = useState(true); - const tableRef = useRef(); + const { services: { application: { @@ -127,30 +103,47 @@ export const RulesTables = React.memo( }, }, } = useKibana(); + + const tableRef = useRef(); + const [defaultAutoRefreshSetting] = useUiSetting$<{ on: boolean; value: number; idleTimeout: number; }>(DEFAULT_RULES_TABLE_REFRESH_SETTING); - const [ - { - exportRuleIds, - filterOptions, - loadingRuleIds, - loadingRulesAction, - pagination, - rules, - selectedRuleIds, - lastUpdated, - showIdleModal, - isRefreshOn, + + const rulesTable = useRulesTable({ + tableRef, + initialStateOverride: { + isRefreshOn: defaultAutoRefreshSetting.on, }, - dispatch, - ] = useReducer(allRulesReducer(tableRef), { - ...initialState, - lastUpdated: Date.now(), - isRefreshOn: defaultAutoRefreshSetting.on, }); + + const { + exportRuleIds, + filterOptions, + loadingRuleIds, + loadingRulesAction, + pagination, + rules, + selectedRuleIds, + lastUpdated, + showIdleModal, + isRefreshOn, + } = rulesTable.state; + + const { + dispatch, + updateOptions, + actionStopped, + setShowIdleModal, + setLastRefreshDate, + setAutoRefreshOn, + reFetchRules, + } = rulesTable; + + const isLoadingRules = loadingRulesAction === 'load'; + const { loading: isLoadingRulesStatuses, rulesStatuses } = useRulesStatuses(rules); const [, dispatchToaster] = useStateToaster(); const mlCapabilities = useMlCapabilities(); @@ -158,40 +151,6 @@ export const RulesTables = React.memo( // TODO: Refactor license check + hasMlAdminPermissions to common check const hasMlPermissions = hasMlLicense(mlCapabilities) && hasMlAdminPermissions(mlCapabilities); - const setRules = useCallback((newRules: Rule[], newPagination: Partial) => { - dispatch({ - type: 'setRules', - rules: newRules, - pagination: newPagination, - }); - }, []); - - const setShowIdleModal = useCallback((show: boolean) => { - dispatch({ - type: 'setShowIdleModal', - show, - }); - }, []); - - const setLastRefreshDate = useCallback(() => { - dispatch({ - type: 'setLastRefreshDate', - }); - }, []); - - const setAutoRefreshOn = useCallback((on: boolean) => { - dispatch({ - type: 'setAutoRefreshOn', - on, - }); - }, []); - - const [isLoadingRules, , reFetchRules] = useRules({ - pagination, - filterOptions, - dispatchRulesInReducer: setRules, - }); - const sorting = useMemo( (): SortingType => ({ sort: { @@ -250,18 +209,24 @@ export const RulesTables = React.memo( [pagination] ); + const onFilterChangedCallback = useCallback( + (newFilter: Partial) => { + updateOptions(newFilter, { page: 1 }); + }, + [updateOptions] + ); + const tableOnChangeCallback = useCallback( ({ page, sort }: EuiBasicTableOnChange) => { - dispatch({ - type: 'updateFilterOptions', - filterOptions: { + updateOptions( + { sortField: (sort?.field as RulesSortingFields) ?? INITIAL_SORT_FIELD, // Narrowing EuiBasicTable sorting types sortOrder: sort?.direction ?? 'desc', }, - pagination: { page: page.index + 1, perPage: page.size }, - }); + { page: page.index + 1, perPage: page.size } + ); }, - [dispatch] + [updateOptions] ); const rulesColumns = useMemo(() => { @@ -324,19 +289,9 @@ export const RulesTables = React.memo( onSelectionChange: (selected: Rule[]) => dispatch({ type: 'selectedRuleIds', ids: selected.map((r) => r.id) }), }), - [loadingRuleIds] + [loadingRuleIds, dispatch] ); - const onFilterChangedCallback = useCallback((newFilterOptions: Partial) => { - dispatch({ - type: 'updateFilterOptions', - filterOptions: { - ...newFilterOptions, - }, - pagination: { page: 1 }, - }); - }, []); - const isLoadingAnActionOnRule = useMemo(() => { if ( loadingRuleIds.length > 0 && @@ -412,7 +367,7 @@ export const RulesTables = React.memo( const handleGenericDownloaderSuccess = useCallback( (exportCount) => { - dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); + actionStopped(); dispatchToaster({ type: 'addToaster', toast: { @@ -423,7 +378,7 @@ export const RulesTables = React.memo( }, }); }, - [dispatchToaster] + [actionStopped, dispatchToaster] ); return ( From 2037c1b7b1e600ef1437a11b6f65c13c301774b8 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Wed, 17 Feb 2021 16:42:47 -0800 Subject: [PATCH 18/93] [DOCS] Fixes section for adding data (#91728) --- docs/user/introduction.asciidoc | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index 65a8c4f6eb1878..50eb81279fc16e 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -36,19 +36,20 @@ No matter your data, {kib} can help you uncover patterns and relationships and v [[kibana-home-page]] === Where to start -Start with the home page, where you’re guided toward the most common use cases. -For a quick reference of {kib} use cases, refer to <> +Start with the home page, where you’re presented options for adding your data. +You can collect data from an app or service or upload a file that contains your data. +If you’re not ready to use your own data, you can add a sample data set. + +The home page provides access to the *Enterprise Search*, *Observability*, and *Security* solutions, +and everything you need to visualize and analyze your data. [role="screenshot"] image::images/home-page.png[Kibana home page] -The main menu gets you to where you need to go. Like the home page, -the menu is organized by use case. Want to work with your logs, metrics, APM, or -Uptime data? The apps you need are under *Observability*. The main menu also includes -*Recently viewed*, so you can easily access your previously opened apps. - -Hidden by default, you open the main menu by clicking the -hamburger icon. To keep the main menu visible at all times, click the *Dock navigation* item. +To access all of {kib} features, use the main menu. +Open this menu by clicking the +menu icon. To keep the main menu visible at all times, click *Dock navigation*. +For a quick reference of all {kib} features, refer to <> [role="screenshot"] image::images/kibana-main-menu.png[Kibana main menu] @@ -91,8 +92,7 @@ image::images/visualization-journey.png[User data analysis journey] | *1* | *Add data.* The best way to add {es} data to {kib} is to use one of our guided processes, -available from the <>. You can collect data from an app or service, upload a -file, or add a sample data set. +available from the <>. | *2* | *Explore.* With <>, you can search your data for hidden From 0337d7d93342449045dcf72f1ba31c85caa01ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 17 Feb 2021 19:45:30 -0500 Subject: [PATCH 19/93] [APM] Domain for charts is based on query range (#91755) * fixing rounding issue * addressing PR comments --- .../shared/charts/timeseries_chart.tsx | 9 +++------ .../context/url_params_context/helpers.test.ts | 18 +++++++++--------- .../context/url_params_context/helpers.ts | 11 +++++------ .../url_params_context.test.tsx | 16 ++++++++-------- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx index 7bfe17e82bf4ae..8009f288d48c0b 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx @@ -24,7 +24,6 @@ import { } from '@elastic/charts'; import { EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import moment from 'moment'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { useChartTheme } from '../../../../../observability/public'; @@ -32,7 +31,6 @@ import { asAbsoluteDateTime } from '../../../../common/utils/formatters'; import { RectCoordinate, TimeSeries } from '../../../../typings/timeseries'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { useTheme } from '../../../hooks/use_theme'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useAnnotationsContext } from '../../../context/annotations/use_annotations_context'; import { useChartPointerEventContext } from '../../../context/chart_pointer_event/use_chart_pointer_event_context'; import { unit } from '../../../style/variables'; @@ -78,14 +76,13 @@ export function TimeseriesChart({ const history = useHistory(); const { annotations } = useAnnotationsContext(); const { setPointerEvent, chartRef } = useChartPointerEventContext(); - const { urlParams } = useUrlParams(); const theme = useTheme(); const chartTheme = useChartTheme(); - const { start, end } = urlParams; + const xValues = timeseries.flatMap(({ data }) => data.map(({ x }) => x)); - const min = moment.utc(start).valueOf(); - const max = moment.utc(end).valueOf(); + const min = Math.min(...xValues); + const max = Math.max(...xValues); const xFormatter = niceTimeFormatter([min, max]); diff --git a/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts index bdce54ad086342..4de68a5bc20362 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts @@ -13,7 +13,7 @@ describe('url_params_context helpers', () => { describe('getDateRange', () => { describe('with non-rounded dates', () => { describe('one minute', () => { - it('rounds the values', () => { + it('rounds the start value to minute', () => { expect( helpers.getDateRange({ state: {}, @@ -21,13 +21,13 @@ describe('url_params_context helpers', () => { rangeTo: '2021-01-28T05:48:55.304Z', }) ).toEqual({ - start: '2021-01-28T05:47:50.000Z', - end: '2021-01-28T05:49:00.000Z', + start: '2021-01-28T05:47:00.000Z', + end: '2021-01-28T05:48:55.304Z', }); }); }); describe('one day', () => { - it('rounds the values', () => { + it('rounds the start value to minute', () => { expect( helpers.getDateRange({ state: {}, @@ -35,14 +35,14 @@ describe('url_params_context helpers', () => { rangeTo: '2021-01-28T05:46:13.367Z', }) ).toEqual({ - start: '2021-01-27T03:00:00.000Z', - end: '2021-01-28T06:00:00.000Z', + start: '2021-01-27T05:46:00.000Z', + end: '2021-01-28T05:46:13.367Z', }); }); }); describe('one year', () => { - it('rounds the values', () => { + it('rounds the start value to minute', () => { expect( helpers.getDateRange({ state: {}, @@ -50,8 +50,8 @@ describe('url_params_context helpers', () => { rangeTo: '2021-01-28T05:52:39.741Z', }) ).toEqual({ - start: '2020-01-01T00:00:00.000Z', - end: '2021-02-01T00:00:00.000Z', + start: '2020-01-28T05:52:00.000Z', + end: '2021-01-28T05:52:39.741Z', }); }); }); diff --git a/x-pack/plugins/apm/public/context/url_params_context/helpers.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.ts index f77c4119f46e1a..eae9eba8b3ddad 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/helpers.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/helpers.ts @@ -6,8 +6,8 @@ */ import datemath from '@elastic/datemath'; -import { scaleUtc } from 'd3-scale'; import { compact, pickBy } from 'lodash'; +import moment from 'moment'; import { IUrlParams } from './types'; function getParsedDate(rawDate?: string, options = {}) { @@ -42,13 +42,12 @@ export function getDateRange({ return { start: state.start, end: state.end }; } - // Calculate ticks for the time ranges to produce nicely rounded values. - const ticks = scaleUtc().domain([start, end]).nice().ticks(); + // rounds down start to minute + const roundedStart = moment(start).startOf('minute'); - // Return the first and last tick values. return { - start: ticks[0].toISOString(), - end: ticks[ticks.length - 1].toISOString(), + start: roundedStart.toISOString(), + end: end.toISOString(), }; } diff --git a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx index 66e22f32b26bd5..056aabb10f8784 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx @@ -52,8 +52,8 @@ describe('UrlParamsContext', () => { const params = getDataFromOutput(wrapper); expect([params.start, params.end]).toEqual([ - '2010-03-15T00:00:00.000Z', - '2010-04-11T00:00:00.000Z', + '2010-03-15T12:00:00.000Z', + '2010-04-10T12:00:00.000Z', ]); }); @@ -71,8 +71,8 @@ describe('UrlParamsContext', () => { const params = getDataFromOutput(wrapper); expect([params.start, params.end]).toEqual([ - '2009-03-15T00:00:00.000Z', - '2009-04-11T00:00:00.000Z', + '2009-03-15T12:00:00.000Z', + '2009-04-10T12:00:00.000Z', ]); }); @@ -92,7 +92,7 @@ describe('UrlParamsContext', () => { expect([params.start, params.end]).toEqual([ '1969-12-31T00:00:00.000Z', - '1970-01-01T00:00:00.000Z', + '1969-12-31T23:59:59.999Z', ]); nowSpy.mockRestore(); @@ -145,8 +145,8 @@ describe('UrlParamsContext', () => { const params = getDataFromOutput(wrapper); expect([params.start, params.end]).toEqual([ - '2005-09-19T00:00:00.000Z', - '2005-10-23T00:00:00.000Z', + '2005-09-20T12:00:00.000Z', + '2005-10-21T12:00:00.000Z', ]); }); @@ -196,7 +196,7 @@ describe('UrlParamsContext', () => { expect([params.start, params.end]).toEqual([ '2000-06-14T00:00:00.000Z', - '2000-06-15T00:00:00.000Z', + '2000-06-14T23:59:59.999Z', ]); }); }); From 2bb2629fec30eb87ef29bfbd8c57f6fa51ce2264 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 18 Feb 2021 01:54:39 +0100 Subject: [PATCH 20/93] [ML] Transforms: Adds missing bucket checkbox to group by popover form. (#91650) Adds missing bucket checkbox to group by popover form. --- ...-plugin-core-public.doclinksstart.links.md | 2 + ...kibana-plugin-core-public.doclinksstart.md | 3 +- .../public/doc_links/doc_links_service.ts | 4 + src/core/public/public.api.md | 2 + .../app/hooks/use_documentation_links.ts | 1 + .../__snapshots__/popover_form.test.tsx.snap | 97 +++---------------- .../group_by_list/popover_form.test.tsx | 29 ++++-- .../components/group_by_list/popover_form.tsx | 56 +++++++++-- 8 files changed, 94 insertions(+), 100 deletions(-) diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 017e3ec57d3404..54c065480b113c 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -53,6 +53,8 @@ readonly links: { readonly base: string; }; readonly aggs: { + readonly composite: string; + readonly composite_missing_bucket: string; readonly date_histogram: string; readonly date_range: string; readonly date_format_pattern: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index dc6804b0630bd4..0bca16a0bb7107 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,4 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: string;
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
putComponentTemplateMetadata: string;
putWatch: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: string;
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putWatch: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
} | | + diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 0d40899544c085..937a89e12b7553 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -72,6 +72,8 @@ export class DocLinksService { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/winlogbeat/${DOC_LINK_VERSION}`, }, aggs: { + composite: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-composite-aggregation.html`, + composite_missing_bucket: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-composite-aggregation.html#_missing_bucket`, date_histogram: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-datehistogram-aggregation.html`, date_range: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-daterange-aggregation.html`, date_format_pattern: `${ELASTICSEARCH_DOCS}search-aggregations-bucket-daterange-aggregation.html#date-format-pattern`, @@ -301,6 +303,8 @@ export interface DocLinksStart { readonly base: string; }; readonly aggs: { + readonly composite: string; + readonly composite_missing_bucket: string; readonly date_histogram: string; readonly date_range: string; readonly date_format_pattern: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 2e23b26f636c8f..e29173d1495af7 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -523,6 +523,8 @@ export interface DocLinksStart { readonly base: string; }; readonly aggs: { + readonly composite: string; + readonly composite_missing_bucket: string; readonly date_histogram: string; readonly date_range: string; readonly date_format_pattern: string; diff --git a/x-pack/plugins/transform/public/app/hooks/use_documentation_links.ts b/x-pack/plugins/transform/public/app/hooks/use_documentation_links.ts index ad11f092e8e578..ded14a2c0e69e2 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_documentation_links.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_documentation_links.ts @@ -11,6 +11,7 @@ export const useDocumentationLinks = () => { const deps = useAppDependencies(); const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = deps.docLinks; return { + esAggsCompositeMissingBucket: deps.docLinks.links.aggs.composite_missing_bucket, esIndicesCreateIndex: deps.docLinks.links.apis.createIndex, esPluginDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`, esQueryDsl: deps.docLinks.links.query.queryDsl, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap index ba53bb2da57160..9c9fb59eea4b1d 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/__snapshots__/popover_form.test.tsx.snap @@ -1,93 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Transform: Group By Minimal initialization 1`] = ` - - - - - - - - - - Apply - - - + onChange={[Function]} + options={Object {}} + otherAggNames={Array []} +/> `; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx index 036342ef16344b..baf0bbd655925c 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.test.tsx @@ -8,6 +8,12 @@ import { shallow } from 'enzyme'; import React from 'react'; +import { KibanaContextProvider } from '../../../../../../../../../src/plugins/kibana_react/public'; + +import { coreMock } from '../../../../../../../../../src/core/public/mocks'; +import { dataPluginMock } from '../../../../../../../../../src/plugins/data/public/mocks'; +const startMock = coreMock.createStart(); + import { AggName } from '../../../../../../common/types/aggregations'; import { PIVOT_SUPPORTED_GROUP_BY_AGGS, PivotGroupByConfig } from '../../../../common'; @@ -88,15 +94,24 @@ describe('Transform: Group By ', () => { const otherAggNames: AggName[] = []; const onChange = (item: PivotGroupByConfig) => {}; + // mock services for QueryStringInput + const services = { + ...startMock, + data: dataPluginMock.createStartContract(), + appName: 'the-test-app', + }; + const wrapper = shallow( - + + + ); - expect(wrapper).toMatchSnapshot(); + expect(wrapper.find(PopoverForm)).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.tsx index 34a463c0f71b8c..da74553aa89c2a 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/group_by_list/popover_form.tsx @@ -5,16 +5,19 @@ * 2.0. */ -import React, { Fragment, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { + htmlIdGenerator, EuiButton, + EuiCheckbox, EuiCodeEditor, EuiFieldText, EuiForm, EuiFormRow, + EuiLink, EuiSelect, EuiSpacer, } from '@elastic/eui'; @@ -22,6 +25,8 @@ import { import { AggName } from '../../../../../../common/types/aggregations'; import { dictionaryToArray } from '../../../../../../common/types/common'; +import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; + import { dateHistogramIntervalFormatRegex, getEsAggFromGroupByConfig, @@ -94,6 +99,8 @@ interface Props { } export const PopoverForm: React.FC = ({ defaultData, otherAggNames, onChange, options }) => { + const { esAggsCompositeMissingBucket } = useDocumentationLinks(); + const isUnsupportedAgg = !isPivotGroupByConfigWithUiSupport(defaultData); const [agg, setAgg] = useState(defaultData.agg); @@ -102,9 +109,14 @@ export const PopoverForm: React.FC = ({ defaultData, otherAggNames, onCha isPivotGroupByConfigWithUiSupport(defaultData) ? defaultData.field : '' ); const [interval, setInterval] = useState(getDefaultInterval(defaultData)); + const [missingBucket, setMissingBucket] = useState( + isPivotGroupByConfigWithUiSupport(defaultData) && defaultData.missing_bucket + ); + + const missingBucketSwitchId = useMemo(() => htmlIdGenerator()(), []); function getUpdatedItem(): PivotGroupByConfig { - const updatedItem = { ...defaultData, agg, aggName, field }; + const updatedItem = { ...defaultData, agg, aggName, field, missing_bucket: missingBucket }; if (isGroupByHistogram(updatedItem) && interval !== undefined) { updatedItem.interval = interval; @@ -225,7 +237,7 @@ export const PopoverForm: React.FC = ({ defaultData, otherAggNames, onCha defaultMessage: 'Interval', })} > - + <> {isGroupByHistogram(defaultData) && ( = ({ defaultData, otherAggNames, onCha onChange={(e) => setInterval(e.target.value)} /> )} - + + + )} + {!isUnsupportedAgg && ( + + {i18n.translate('xpack.transform.groupBy.popoverForm.missingBucketCheckboxHelpText', { + defaultMessage: 'Select to include documents without a value.', + })} +
+ + {i18n.translate( + 'xpack.transform.stepDetailsForm.missingBucketCheckboxHelpTextLink', + { + defaultMessage: `Learn more`, + } + )} + + + } + > + setMissingBucket(!missingBucket)} + />
)} {isUnsupportedAgg && ( - + <> = ({ defaultData, otherAggNames, onCha isReadOnly aria-label="Read only code editor" /> - + )} onChange(getUpdatedItem())}> From 9180ed112c5fef8f99822a5be313edae067aa034 Mon Sep 17 00:00:00 2001 From: Justin Ibarra Date: Wed, 17 Feb 2021 16:19:06 -0900 Subject: [PATCH 21/93] [Detection Rules] Remove timestamp_override in endgame promotion rules (#91771) ## Summary Pulls updates from https://github.com/elastic/detection-rules/pull/951. This basically reverts the changes made in #91553 for _only_ the endgame promotion rules ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) --- .../endpoint_adversary_behavior_detected.json | 3 +-- .../prepackaged_rules/endpoint_cred_dumping_detected.json | 3 +-- .../prepackaged_rules/endpoint_cred_dumping_prevented.json | 3 +-- .../prepackaged_rules/endpoint_cred_manipulation_detected.json | 3 +-- .../endpoint_cred_manipulation_prevented.json | 3 +-- .../rules/prepackaged_rules/endpoint_exploit_detected.json | 3 +-- .../rules/prepackaged_rules/endpoint_exploit_prevented.json | 3 +-- .../rules/prepackaged_rules/endpoint_malware_detected.json | 3 +-- .../rules/prepackaged_rules/endpoint_malware_prevented.json | 3 +-- .../prepackaged_rules/endpoint_permission_theft_detected.json | 3 +-- .../prepackaged_rules/endpoint_permission_theft_prevented.json | 3 +-- .../prepackaged_rules/endpoint_process_injection_detected.json | 3 +-- .../endpoint_process_injection_prevented.json | 3 +-- .../rules/prepackaged_rules/endpoint_ransomware_detected.json | 3 +-- .../rules/prepackaged_rules/endpoint_ransomware_prevented.json | 3 +-- 15 files changed, 15 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_adversary_behavior_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_adversary_behavior_detected.json index 6cf46e35595de5..8084067b3a6d28 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_adversary_behavior_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_adversary_behavior_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_detected.json index 69ba61dea32287..9c28d065b322d4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_prevented.json index 323ad5c0f446b7..352712e38f42de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_dumping_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_detected.json index 9ca5a3da5ae213..259bcd51aeb3e2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_prevented.json index 4923e42d16d9c5..19348062b10f1d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_cred_manipulation_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_detected.json index 9c9fcc559bea7f..2fd3aaa0d8a576 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_prevented.json index 50861dba7f3fb4..8f90e1162546b0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_exploit_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_detected.json index 72d52a97273207..3d740f8b7064f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_prevented.json index 7af172d90eb30c..33195c7fcbecca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_malware_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_detected.json index 28f473795299a5..fac13a6d358dd2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_prevented.json index 09c9f83f95622b..a2d8700076c234 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_permission_theft_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_detected.json index af235ea2022cf1..ef4f29067b0c5f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_prevented.json index cd16caf11482b1..b22751e35c053d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_process_injection_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_detected.json index 8353cc06972e21..3b973f42bbca52 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_detected.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_prevented.json index 590c6b08140677..b6458b73e80153 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_prevented.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endpoint_ransomware_prevented.json @@ -19,7 +19,6 @@ "Elastic", "Endpoint Security" ], - "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 4 } From 97d391a6360f029b03af255ef605d895ffaa8863 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Thu, 18 Feb 2021 01:32:40 +0000 Subject: [PATCH 22/93] [Security Solution] Detection rules for case UI (#91434) * Adding type field to client * Removing context and adding association type * Handle alerts from multiple indices * Adding flow for adding a sub case * Making progress on creating alerts from rules * Refactored add comment to handle case and sub case * Starting sub case API and refactoring of case client * Fleshing out find cases * Finished the find cases api * Filtering comments by association type * Fixing tests and types * Updating snapshots * Cleaning up comment references * Working unit tests * Fixing integration tests and got ES to work * Unit tests and api integration test working * Refactoring find and get_status * Starting patch, and update * script for sub cases * Removing converted_by and fixing type errors * Adding docs for script * Removing converted_by and fixing integration test * init expanded rows * Adding sub case id to comment routes * Removing stringify comparison * styling * clean up * add status column * styling * hide actions if it has sub-cases * Adding delete api and tests * generated alert * Updating license * missed license files * Integration tests passing * Adding more tests for sub cases * wip * Find int tests, scoped client, patch sub user actions * fixing types and call cluster * fixing get sub case param issue * Adding user actions for sub cases * Preventing alerts on collections and refactoring user * Allowing type to be updated for ind cases * subcases attached to api * combine enum on UI for simplification * Refactoring and writing tests * Fixing sub case status filtering * add alerts count * Adding more tests not allowing gen alerts patch * Working unit tests * Push to connector gets all sub case comments * Writing more tests and cleaning up * Updating push functionality for generated alerts and sub cases * Adding comment about updating collection sync * use CaseType to check if it is a sub-case * fix types and disable selection if it has subcases * isEmpty * Detection rule correctly adding alerts to sub case * update api and functionality to accept sub case * integration part I * fix integration with case connector * Fix manual attach * Fix types * Fix bug when updating * Fix bug with user actions * Fix react key error * Fix bug when pushing a lot of alerts * fix lint error * Fix limit * fix title on sub case * fix unit tests * rm bazel * fix unit tests and cypress test * enable delete case icon * revert change * review * Fix the scripts alerts generation code * temp work * Fix rule types and add migration * fix types * fix types error * Remove query alerts * Fix rules * fix types * fix lint error * fix types * delete a sub case * rm unused i18n * fix delete cases * fix unit tests * fix unit test * update Case type * fix types * fix unit test * final integration between rule and case * fix integration test * fix unit test + bring back connector in action of rule Co-authored-by: Jonathan Buttner Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Christos Nasikas --- x-pack/plugins/case/common/api/cases/case.ts | 1 + .../plugins/case/common/api/cases/comment.ts | 7 +- .../case/common/api/cases/user_actions.ts | 1 + x-pack/plugins/case/common/api/helpers.ts | 6 + x-pack/plugins/case/common/constants.ts | 10 + .../case/server/client/cases/create.test.ts | 3 + .../plugins/case/server/client/cases/get.ts | 16 +- .../plugins/case/server/client/cases/mock.ts | 4 + .../case/server/client/cases/update.test.ts | 5 + .../plugins/case/server/client/cases/utils.ts | 1 + .../case/server/client/comments/add.test.ts | 24 ++ .../case/server/client/comments/add.ts | 29 ++- x-pack/plugins/case/server/client/types.ts | 1 + .../case/server/client/user_actions/get.ts | 30 ++- .../plugins/case/server/common/utils.test.ts | 24 ++ .../case/server/connectors/case/index.test.ts | 4 + .../case/server/connectors/case/index.ts | 51 +++- .../case/server/connectors/case/schema.ts | 14 +- .../plugins/case/server/connectors/index.ts | 24 ++ .../api/__fixtures__/mock_saved_objects.ts | 8 + .../api/cases/comments/patch_comment.test.ts | 8 + .../api/cases/comments/post_comment.test.ts | 4 + .../routes/api/cases/patch_cases.test.ts | 3 + .../server/routes/api/cases/post_case.test.ts | 1 + .../user_actions/get_all_user_actions.ts | 35 ++- .../plugins/case/server/routes/api/index.ts | 8 +- .../case/server/routes/api/utils.test.ts | 8 + .../plugins/case/server/routes/api/utils.ts | 7 +- .../server/saved_object_types/comments.ts | 10 + .../server/saved_object_types/migrations.ts | 6 +- .../case/server/scripts/sub_cases/index.ts | 31 ++- .../case/server/services/alerts/index.ts | 7 +- .../server/services/user_actions/index.ts | 15 +- .../security_solution/common/constants.ts | 2 +- .../components/add_comment/index.test.tsx | 7 +- .../cases/components/add_comment/index.tsx | 15 +- .../cases/components/all_cases/actions.tsx | 64 ++--- .../cases/components/all_cases/columns.tsx | 45 +++- .../components/all_cases/expanded_row.tsx | 68 +++++ .../cases/components/all_cases/helpers.ts | 37 +++ .../cases/components/all_cases/index.test.tsx | 4 + .../cases/components/all_cases/index.tsx | 39 ++- .../components/all_cases/translations.ts | 4 + .../components/case_view/helpers.test.tsx | 22 +- .../cases/components/case_view/helpers.ts | 41 +-- .../cases/components/case_view/index.test.tsx | 8 + .../cases/components/case_view/index.tsx | 76 +----- .../components/confirm_delete_case/index.tsx | 10 +- .../confirm_delete_case/translations.ts | 5 + .../connectors/case/alert_fields.tsx | 4 +- .../connectors/case/existing_case.tsx | 99 ++++++-- .../connectors/case/translations.ts | 14 ++ .../cases/components/create/form_context.tsx | 11 +- .../public/cases/components/create/mock.ts | 3 +- .../add_to_case_action.test.tsx | 18 +- .../timeline_actions/add_to_case_action.tsx | 19 +- .../create_case_modal.tsx | 5 +- .../use_create_case_modal/index.tsx | 10 +- .../components/user_action_tree/helpers.tsx | 236 ++++++++++++++++-- .../components/user_action_tree/index.tsx | 97 +++++-- .../user_action_tree/translations.ts | 26 +- .../user_action_alert_comment_event.test.tsx | 28 +-- .../user_action_alert_comment_event.tsx | 58 ++++- .../user_action_copy_link.tsx | 14 +- .../user_action_show_alert.test.tsx | 16 +- .../user_action_show_alert.tsx | 23 +- .../public/cases/containers/api.ts | 98 +++++++- .../public/cases/containers/mock.ts | 8 +- .../public/cases/containers/types.ts | 30 ++- .../cases/containers/use_delete_cases.tsx | 10 +- .../public/cases/containers/use_get_case.tsx | 40 +-- .../containers/use_get_case_user_actions.tsx | 56 +++-- .../containers/use_post_comment.test.tsx | 55 +++- .../cases/containers/use_post_comment.tsx | 12 +- .../cases/containers/use_update_case.test.tsx | 24 +- .../cases/containers/use_update_case.tsx | 72 ++++-- .../containers/use_update_comment.test.tsx | 29 ++- .../cases/containers/use_update_comment.tsx | 5 +- .../public/cases/pages/case_details.tsx | 11 +- .../public/cases/pages/index.tsx | 12 +- .../components/link_to/redirect_to_case.tsx | 32 ++- .../public/common/components/links/index.tsx | 9 +- .../components/alerts_table/actions.test.tsx | 4 +- .../components/alerts_table/actions.tsx | 236 +++++++++++++----- .../investigate_in_timeline_action.tsx | 63 +++-- .../components/alerts_table/types.ts | 2 +- .../detection_engine/alerts/use_query.tsx | 5 +- .../signals/single_bulk_create.ts | 16 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../tests/cases/comments/patch_comment.ts | 24 ++ .../tests/cases/comments/post_comment.ts | 16 ++ .../basic/tests/cases/patch_cases.ts | 16 ++ .../basic/tests/connectors/case.ts | 28 ++- .../case_api_integration/common/lib/mock.ts | 8 +- 95 files changed, 1916 insertions(+), 541 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts diff --git a/x-pack/plugins/case/common/api/cases/case.ts b/x-pack/plugins/case/common/api/cases/case.ts index 49643ca1f4d0c7..33a93952b0e2d7 100644 --- a/x-pack/plugins/case/common/api/cases/case.ts +++ b/x-pack/plugins/case/common/api/cases/case.ts @@ -123,6 +123,7 @@ export const CaseResponseRt = rt.intersection([ version: rt.string, }), rt.partial({ + subCaseIds: rt.array(rt.string), subCases: rt.array(SubCaseResponseRt), comments: rt.array(CommentResponseRt), }), diff --git a/x-pack/plugins/case/common/api/cases/comment.ts b/x-pack/plugins/case/common/api/cases/comment.ts index cfc6099fa4bb57..41ad0e87f14d2f 100644 --- a/x-pack/plugins/case/common/api/cases/comment.ts +++ b/x-pack/plugins/case/common/api/cases/comment.ts @@ -52,7 +52,11 @@ export const ContextTypeUserRt = rt.type({ export const AlertCommentRequestRt = rt.type({ type: rt.union([rt.literal(CommentType.generatedAlert), rt.literal(CommentType.alert)]), alertId: rt.union([rt.array(rt.string), rt.string]), - index: rt.string, + index: rt.union([rt.array(rt.string), rt.string]), + rule: rt.type({ + id: rt.union([rt.string, rt.null]), + name: rt.union([rt.string, rt.null]), + }), }); const AttributesTypeUserRt = rt.intersection([ContextTypeUserRt, CommentAttributesBasicRt]); @@ -108,6 +112,7 @@ export const CommentsResponseRt = rt.type({ export const AllCommentsResponseRt = rt.array(CommentResponseRt); +export type AttributesTypeAlerts = rt.TypeOf; export type CommentAttributes = rt.TypeOf; export type CommentRequest = rt.TypeOf; export type CommentResponse = rt.TypeOf; diff --git a/x-pack/plugins/case/common/api/cases/user_actions.ts b/x-pack/plugins/case/common/api/cases/user_actions.ts index de9e88993df9a2..6c8e0de80903df 100644 --- a/x-pack/plugins/case/common/api/cases/user_actions.ts +++ b/x-pack/plugins/case/common/api/cases/user_actions.ts @@ -49,6 +49,7 @@ const CaseUserActionResponseRT = rt.intersection([ case_id: rt.string, comment_id: rt.union([rt.string, rt.null]), }), + rt.partial({ sub_case_id: rt.string }), ]); export const CaseUserActionAttributesRt = CaseUserActionBasicRT; diff --git a/x-pack/plugins/case/common/api/helpers.ts b/x-pack/plugins/case/common/api/helpers.ts index 9c290c0a4d6128..00c8ff402c802e 100644 --- a/x-pack/plugins/case/common/api/helpers.ts +++ b/x-pack/plugins/case/common/api/helpers.ts @@ -13,6 +13,7 @@ import { SUB_CASE_DETAILS_URL, SUB_CASES_URL, CASE_PUSH_URL, + SUB_CASE_USER_ACTIONS_URL, } from '../constants'; export const getCaseDetailsUrl = (id: string): string => { @@ -38,6 +39,11 @@ export const getCaseCommentDetailsUrl = (caseId: string, commentId: string): str export const getCaseUserActionUrl = (id: string): string => { return CASE_USER_ACTIONS_URL.replace('{case_id}', id); }; + +export const getSubCaseUserActionUrl = (caseID: string, subCaseID: string): string => { + return SUB_CASE_USER_ACTIONS_URL.replace('{case_id}', caseID).replace('{sub_case_id}', subCaseID); +}; + export const getCasePushUrl = (caseId: string, connectorId: string): string => { return CASE_PUSH_URL.replace('{case_id}', caseId).replace('{connector_id}', connectorId); }; diff --git a/x-pack/plugins/case/common/constants.ts b/x-pack/plugins/case/common/constants.ts index 5d34ed120ff6f8..cc69c7ecc29094 100644 --- a/x-pack/plugins/case/common/constants.ts +++ b/x-pack/plugins/case/common/constants.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { DEFAULT_MAX_SIGNALS } from '../../security_solution/common/constants'; + export const APP_ID = 'case'; /** @@ -19,6 +21,7 @@ export const CASE_CONFIGURE_CONNECTORS_URL = `${CASE_CONFIGURE_URL}/connectors`; export const SUB_CASES_PATCH_DEL_URL = `${CASES_URL}/sub_cases`; export const SUB_CASES_URL = `${CASE_DETAILS_URL}/sub_cases`; export const SUB_CASE_DETAILS_URL = `${CASE_DETAILS_URL}/sub_cases/{sub_case_id}`; +export const SUB_CASE_USER_ACTIONS_URL = `${SUB_CASE_DETAILS_URL}/user_actions`; export const CASE_COMMENTS_URL = `${CASE_DETAILS_URL}/comments`; export const CASE_COMMENT_DETAILS_URL = `${CASE_DETAILS_URL}/comments/{comment_id}`; @@ -45,3 +48,10 @@ export const SUPPORTED_CONNECTORS = [ JIRA_ACTION_TYPE_ID, RESILIENT_ACTION_TYPE_ID, ]; + +/** + * Alerts + */ + +export const MAX_ALERTS_PER_SUB_CASE = 5000; +export const MAX_GENERATED_ALERTS_PER_SUB_CASE = MAX_ALERTS_PER_SUB_CASE / DEFAULT_MAX_SIGNALS; diff --git a/x-pack/plugins/case/server/client/cases/create.test.ts b/x-pack/plugins/case/server/client/cases/create.test.ts index 065825472954b8..3016a57f218757 100644 --- a/x-pack/plugins/case/server/client/cases/create.test.ts +++ b/x-pack/plugins/case/server/client/cases/create.test.ts @@ -76,6 +76,7 @@ describe('create', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -174,6 +175,7 @@ describe('create', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -239,6 +241,7 @@ describe('create', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", diff --git a/x-pack/plugins/case/server/client/cases/get.ts b/x-pack/plugins/case/server/client/cases/get.ts index eab43a0c4d4536..ab0b97abbcb76a 100644 --- a/x-pack/plugins/case/server/client/cases/get.ts +++ b/x-pack/plugins/case/server/client/cases/get.ts @@ -26,19 +26,24 @@ export const get = async ({ includeComments = false, includeSubCaseComments = false, }: GetParams): Promise => { - const theCase = await caseService.getCase({ - client: savedObjectsClient, - id, - }); + const [theCase, subCasesForCaseId] = await Promise.all([ + caseService.getCase({ + client: savedObjectsClient, + id, + }), + caseService.findSubCasesByCaseId({ client: savedObjectsClient, ids: [id] }), + ]); + + const subCaseIds = subCasesForCaseId.saved_objects.map((so) => so.id); if (!includeComments) { return CaseResponseRt.encode( flattenCaseSavedObject({ savedObject: theCase, + subCaseIds, }) ); } - const theComments = await caseService.getAllCaseComments({ client: savedObjectsClient, id, @@ -53,6 +58,7 @@ export const get = async ({ flattenCaseSavedObject({ savedObject: theCase, comments: theComments.saved_objects, + subCaseIds, totalComment: theComments.total, totalAlerts: countAlertsForID({ comments: theComments, id }), }) diff --git a/x-pack/plugins/case/server/client/cases/mock.ts b/x-pack/plugins/case/server/client/cases/mock.ts index 2be9f410598312..809c4ad1ea1bd7 100644 --- a/x-pack/plugins/case/server/client/cases/mock.ts +++ b/x-pack/plugins/case/server/client/cases/mock.ts @@ -54,6 +54,10 @@ export const commentAlert: CommentResponse = { id: 'mock-comment-1', alertId: 'alert-id-1', index: 'alert-index-1', + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, type: CommentType.alert as const, created_at: '2019-11-25T21:55:00.177Z', created_by: { diff --git a/x-pack/plugins/case/server/client/cases/update.test.ts b/x-pack/plugins/case/server/client/cases/update.test.ts index 53e233c74deb4e..7a3e4458f25c54 100644 --- a/x-pack/plugins/case/server/client/cases/update.test.ts +++ b/x-pack/plugins/case/server/client/cases/update.test.ts @@ -71,6 +71,7 @@ describe('update', () => { "syncAlerts": true, }, "status": "closed", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -166,6 +167,7 @@ describe('update', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -233,6 +235,7 @@ describe('update', () => { "syncAlerts": true, }, "status": "in-progress", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -300,6 +303,7 @@ describe('update', () => { "syncAlerts": true, }, "status": "closed", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -371,6 +375,7 @@ describe('update', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", diff --git a/x-pack/plugins/case/server/client/cases/utils.ts b/x-pack/plugins/case/server/client/cases/utils.ts index 78bdc6d282c698..fda4142bf77c7b 100644 --- a/x-pack/plugins/case/server/client/cases/utils.ts +++ b/x-pack/plugins/case/server/client/cases/utils.ts @@ -314,6 +314,7 @@ export const getCommentContextFromAttributes = ( type: attributes.type, alertId: attributes.alertId, index: attributes.index, + rule: attributes.rule, }; default: return { diff --git a/x-pack/plugins/case/server/client/comments/add.test.ts b/x-pack/plugins/case/server/client/comments/add.test.ts index 315203a1f5e1d9..c9b1e4fd132722 100644 --- a/x-pack/plugins/case/server/client/comments/add.test.ts +++ b/x-pack/plugins/case/server/client/comments/add.test.ts @@ -75,6 +75,10 @@ describe('addComment', () => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: 'test-rule1', + name: 'test-rule', + }, }, }); @@ -94,6 +98,10 @@ describe('addComment', () => { "index": "test-index", "pushed_at": null, "pushed_by": null, + "rule": Object { + "id": "test-rule1", + "name": "test-rule", + }, "type": "alert", "updated_at": null, "updated_by": null, @@ -231,6 +239,10 @@ describe('addComment', () => { type: CommentType.alert, alertId: 'test-alert', index: 'test-index', + rule: { + id: 'test-rule1', + name: 'test-rule', + }, }, }); @@ -265,6 +277,10 @@ describe('addComment', () => { type: CommentType.alert, alertId: 'test-alert', index: 'test-index', + rule: { + id: 'test-rule1', + name: 'test-rule', + }, }, }); @@ -406,6 +422,10 @@ describe('addComment', () => { type: CommentType.alert, index: 'test-index', alertId: 'test-id', + rule: { + id: 'test-rule1', + name: 'test-rule', + }, }, }) .catch((e) => { @@ -478,6 +498,10 @@ describe('addComment', () => { type: CommentType.alert, alertId: 'test-alert', index: 'test-index', + rule: { + id: 'test-rule1', + name: 'test-rule', + }, }, }) .catch((e) => { diff --git a/x-pack/plugins/case/server/client/comments/add.ts b/x-pack/plugins/case/server/client/comments/add.ts index 7dd1b4a8f6c5cd..0a86c1825fedce 100644 --- a/x-pack/plugins/case/server/client/comments/add.ts +++ b/x-pack/plugins/case/server/client/comments/add.ts @@ -38,6 +38,8 @@ import { import { CaseServiceSetup, CaseUserActionServiceSetup } from '../../services'; import { CommentableCase } from '../../common'; import { CaseClientHandler } from '..'; +import { CASE_COMMENT_SAVED_OBJECT } from '../../saved_object_types'; +import { MAX_GENERATED_ALERTS_PER_SUB_CASE } from '../../../common/constants'; async function getSubCase({ caseService, @@ -56,7 +58,20 @@ async function getSubCase({ }): Promise> { const mostRecentSubCase = await caseService.getMostRecentSubCase(savedObjectsClient, caseId); if (mostRecentSubCase && mostRecentSubCase.attributes.status !== CaseStatuses.closed) { - return mostRecentSubCase; + const subCaseAlertsAttachement = await caseService.getAllSubCaseComments({ + client: savedObjectsClient, + id: mostRecentSubCase.id, + options: { + fields: [], + filter: `${CASE_COMMENT_SAVED_OBJECT}.attributes.type: ${CommentType.generatedAlert}`, + page: 1, + perPage: 1, + }, + }); + + if (subCaseAlertsAttachement.total <= MAX_GENERATED_ALERTS_PER_SUB_CASE) { + return mostRecentSubCase; + } } const newSubCase = await caseService.createSubCase({ @@ -160,7 +175,11 @@ const addGeneratedAlerts = async ({ await caseClient.updateAlertsStatus({ ids, status: subCase.attributes.status, - indices: new Set([newComment.attributes.index]), + indices: new Set([ + ...(Array.isArray(newComment.attributes.index) + ? newComment.attributes.index + : [newComment.attributes.index]), + ]), }); } @@ -282,7 +301,11 @@ export const addComment = async ({ await caseClient.updateAlertsStatus({ ids, status: updatedCase.status, - indices: new Set([newComment.attributes.index]), + indices: new Set([ + ...(Array.isArray(newComment.attributes.index) + ? newComment.attributes.index + : [newComment.attributes.index]), + ]), }); } diff --git a/x-pack/plugins/case/server/client/types.ts b/x-pack/plugins/case/server/client/types.ts index a8f64227daf83f..ba5677426c2228 100644 --- a/x-pack/plugins/case/server/client/types.ts +++ b/x-pack/plugins/case/server/client/types.ts @@ -59,6 +59,7 @@ export interface CaseClientGetAlerts { export interface CaseClientGetUserActions { caseId: string; + subCaseId?: string; } export interface MappingsClient { diff --git a/x-pack/plugins/case/server/client/user_actions/get.ts b/x-pack/plugins/case/server/client/user_actions/get.ts index 8a4e45f71b9ca3..f6371b8e8b1e7b 100644 --- a/x-pack/plugins/case/server/client/user_actions/get.ts +++ b/x-pack/plugins/case/server/client/user_actions/get.ts @@ -6,7 +6,11 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { CASE_SAVED_OBJECT, CASE_COMMENT_SAVED_OBJECT } from '../../saved_object_types'; +import { + CASE_SAVED_OBJECT, + CASE_COMMENT_SAVED_OBJECT, + SUB_CASE_SAVED_OBJECT, +} from '../../saved_object_types'; import { CaseUserActionsResponseRt, CaseUserActionsResponse } from '../../../common/api'; import { CaseUserActionServiceSetup } from '../../services'; @@ -14,24 +18,36 @@ interface GetParams { savedObjectsClient: SavedObjectsClientContract; userActionService: CaseUserActionServiceSetup; caseId: string; + subCaseId?: string; } export const get = async ({ savedObjectsClient, userActionService, caseId, + subCaseId, }: GetParams): Promise => { const userActions = await userActionService.getUserActions({ client: savedObjectsClient, caseId, + subCaseId, }); return CaseUserActionsResponseRt.encode( - userActions.saved_objects.map((ua) => ({ - ...ua.attributes, - action_id: ua.id, - case_id: ua.references.find((r) => r.type === CASE_SAVED_OBJECT)?.id ?? '', - comment_id: ua.references.find((r) => r.type === CASE_COMMENT_SAVED_OBJECT)?.id ?? null, - })) + userActions.saved_objects.reduce((acc, ua) => { + if (subCaseId == null && ua.references.some((uar) => uar.type === SUB_CASE_SAVED_OBJECT)) { + return acc; + } + return [ + ...acc, + { + ...ua.attributes, + action_id: ua.id, + case_id: ua.references.find((r) => r.type === CASE_SAVED_OBJECT)?.id ?? '', + comment_id: ua.references.find((r) => r.type === CASE_COMMENT_SAVED_OBJECT)?.id ?? null, + sub_case_id: ua.references.find((r) => r.type === SUB_CASE_SAVED_OBJECT)?.id ?? '', + }, + ]; + }, []) ); }; diff --git a/x-pack/plugins/case/server/common/utils.test.ts b/x-pack/plugins/case/server/common/utils.test.ts index d89feb009f8069..5e6a86358de256 100644 --- a/x-pack/plugins/case/server/common/utils.test.ts +++ b/x-pack/plugins/case/server/common/utils.test.ts @@ -99,6 +99,10 @@ describe('common utils', () => { alertId: ['a', 'b', 'c'], index: '', type: CommentType.generatedAlert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, ], }, @@ -118,6 +122,10 @@ describe('common utils', () => { alertId: ['a', 'b', 'c'], index: '', type: CommentType.alert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, ], }, @@ -139,6 +147,10 @@ describe('common utils', () => { alertId: ['a', 'b'], index: '', type: CommentType.alert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, { comment: '', @@ -164,6 +176,10 @@ describe('common utils', () => { alertId: ['a', 'b'], index: '', type: CommentType.alert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, ], }, @@ -197,6 +213,10 @@ describe('common utils', () => { alertId: ['a', 'b'], index: '', type: CommentType.alert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, ], }, @@ -224,6 +244,10 @@ describe('common utils', () => { alertId: ['a', 'b'], index: '', type: CommentType.alert, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, }, ], }, diff --git a/x-pack/plugins/case/server/connectors/case/index.test.ts b/x-pack/plugins/case/server/connectors/case/index.test.ts index 6b7e395bae4dc1..4be519858db18c 100644 --- a/x-pack/plugins/case/server/connectors/case/index.test.ts +++ b/x-pack/plugins/case/server/connectors/case/index.test.ts @@ -717,6 +717,10 @@ describe('case connector', () => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: null, + name: null, + }, }, }, }; diff --git a/x-pack/plugins/case/server/connectors/case/index.ts b/x-pack/plugins/case/server/connectors/case/index.ts index 34b407616cfe48..a64cba567ce46d 100644 --- a/x-pack/plugins/case/server/connectors/case/index.ts +++ b/x-pack/plugins/case/server/connectors/case/index.ts @@ -122,23 +122,48 @@ async function executor( /** * This converts a connector style generated alert ({_id: string} | {_id: string}[]) to the expected format of addComment. */ +interface AttachmentAlerts { + ids: string[]; + indices: string[]; + rule: { id: string | null; name: string | null }; +} export const transformConnectorComment = (comment: CommentSchemaType): CommentRequest => { if (isCommentGeneratedAlert(comment)) { - const alertId: string[] = []; - if (Array.isArray(comment.alerts)) { - alertId.push( - ...comment.alerts.map((alert: { _id: string }) => { - return alert._id; - }) + try { + const genAlerts: Array<{ + _id: string; + _index: string; + ruleId: string | undefined; + ruleName: string | undefined; + }> = JSON.parse( + `${comment.alerts.substring(0, comment.alerts.lastIndexOf('__SEPARATOR__'))}]`.replace( + /__SEPARATOR__/gi, + ',' + ) + ); + + const { ids, indices, rule } = genAlerts.reduce( + (acc, { _id, _index, ruleId, ruleName }) => { + // Mutation is faster than destructing. + // Mutation usually leads to side effects but for this scenario it's ok to do it. + acc.ids.push(_id); + acc.indices.push(_index); + // We assume one rule per batch of alerts + acc.rule = { id: ruleId ?? null, name: ruleName ?? null }; + return acc; + }, + { ids: [], indices: [], rule: { id: null, name: null } } ); - } else { - alertId.push(comment.alerts._id); + + return { + type: CommentType.generatedAlert, + alertId: ids, + index: indices, + rule, + }; + } catch (e) { + throw new Error(`Error parsing generated alert in case connector -> ${e.message}`); } - return { - type: CommentType.generatedAlert, - alertId, - index: comment.index, - }; } else { return comment; } diff --git a/x-pack/plugins/case/server/connectors/case/schema.ts b/x-pack/plugins/case/server/connectors/case/schema.ts index cdeb00209f846b..ac34ad40cfa13a 100644 --- a/x-pack/plugins/case/server/connectors/case/schema.ts +++ b/x-pack/plugins/case/server/connectors/case/schema.ts @@ -17,17 +17,9 @@ const ContextTypeUserSchema = schema.object({ comment: schema.string(), }); -const AlertIDSchema = schema.object( - { - _id: schema.string(), - }, - { unknowns: 'ignore' } -); - const ContextTypeAlertGroupSchema = schema.object({ type: schema.literal(CommentType.generatedAlert), - alerts: schema.oneOf([schema.arrayOf(AlertIDSchema), AlertIDSchema]), - index: schema.string(), + alerts: schema.string(), }); export type ContextTypeGeneratedAlertType = typeof ContextTypeAlertGroupSchema.type; @@ -37,6 +29,10 @@ const ContextTypeAlertSchema = schema.object({ // allowing either an array or a single value to preserve the previous API of attaching a single alert ID alertId: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]), index: schema.string(), + rule: schema.object({ + id: schema.nullable(schema.string()), + name: schema.nullable(schema.string()), + }), }); export type ContextTypeAlertSchemaType = typeof ContextTypeAlertSchema.type; diff --git a/x-pack/plugins/case/server/connectors/index.ts b/x-pack/plugins/case/server/connectors/index.ts index 056ccff2733a76..898d61301a1402 100644 --- a/x-pack/plugins/case/server/connectors/index.ts +++ b/x-pack/plugins/case/server/connectors/index.ts @@ -65,3 +65,27 @@ export const isCommentAlert = ( ): comment is ContextTypeAlertSchemaType => { return comment.type === CommentType.alert; }; + +/** + * Separator field for the case connector alerts string parser. + */ +const separator = '__SEPARATOR__'; + +interface AlertIDIndex { + _id: string; + _index: string; + ruleId: string; + ruleName: string; +} + +/** + * Creates the format that the connector's parser is expecting, it should result in something like this: + * [{"_id":"1","_index":"index1"}__SEPARATOR__{"_id":"id2","_index":"index2"}__SEPARATOR__] + * + * This should only be used for testing purposes. + */ +export function createAlertsString(alerts: AlertIDIndex[]) { + return `[${alerts.reduce((acc, alert) => { + return `${acc}${JSON.stringify(alert)}${separator}`; + }, '')}]`; +} diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts index 2fe0be3e08ede6..e67a6f6dd3344b 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -346,6 +346,10 @@ export const mockCaseComments: Array> = [ }, pushed_at: null, pushed_by: null, + rule: { + id: 'rule-id-1', + name: 'rule-name-1', + }, updated_at: '2019-11-25T22:32:30.608Z', updated_by: { full_name: 'elastic', @@ -379,6 +383,10 @@ export const mockCaseComments: Array> = [ }, pushed_at: null, pushed_by: null, + rule: { + id: 'rule-id-2', + name: 'rule-name-2', + }, updated_at: '2019-11-25T22:32:30.608Z', updated_by: { full_name: 'elastic', diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts index 9dec910f9fc46f..1ebd336c83af75 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts @@ -69,6 +69,10 @@ describe('PATCH comment', () => { type: CommentType.alert, alertId: 'new-id', index: 'test-index', + rule: { + id: 'rule-id', + name: 'rule', + }, id: commentID, version: 'WzYsMV0=', }, @@ -218,6 +222,10 @@ describe('PATCH comment', () => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: 'rule-id', + name: 'rule', + }, id: 'mock-comment-1', version: 'WzEsMV0=', }, diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts index fb51b8f76d0ef4..807ec0d089a524 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts @@ -68,6 +68,10 @@ describe('POST comment', () => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: 'rule-id', + name: 'rule-name', + }, }, }); diff --git a/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts b/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts index e50d14e5c66c4f..b3f87211c95475 100644 --- a/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts @@ -81,6 +81,7 @@ describe('PATCH cases', () => { "syncAlerts": true, }, "status": "closed", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -154,6 +155,7 @@ describe('PATCH cases', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -222,6 +224,7 @@ describe('PATCH cases', () => { "syncAlerts": true, }, "status": "in-progress", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", diff --git a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts index 53829157c5b049..e1669203d3dedb 100644 --- a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts @@ -213,6 +213,7 @@ describe('POST cases', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", diff --git a/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts b/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts index 06e929cc40e6bb..488f32a795811f 100644 --- a/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts +++ b/x-pack/plugins/case/server/routes/api/cases/user_actions/get_all_user_actions.ts @@ -9,9 +9,9 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; -import { CASE_USER_ACTIONS_URL } from '../../../../../common/constants'; +import { CASE_USER_ACTIONS_URL, SUB_CASE_USER_ACTIONS_URL } from '../../../../../common/constants'; -export function initGetAllUserActionsApi({ router }: RouteDeps) { +export function initGetAllCaseUserActionsApi({ router }: RouteDeps) { router.get( { path: CASE_USER_ACTIONS_URL, @@ -39,3 +39,34 @@ export function initGetAllUserActionsApi({ router }: RouteDeps) { } ); } + +export function initGetAllSubCaseUserActionsApi({ router }: RouteDeps) { + router.get( + { + path: SUB_CASE_USER_ACTIONS_URL, + validate: { + params: schema.object({ + case_id: schema.string(), + sub_case_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + if (!context.case) { + return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' }); + } + + const caseClient = context.case.getCaseClient(); + const caseId = request.params.case_id; + const subCaseId = request.params.sub_case_id; + + try { + return response.ok({ + body: await caseClient.getUserActions({ caseId, subCaseId }), + }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/index.ts b/x-pack/plugins/case/server/routes/api/index.ts index f2fd986dd8a3ae..12d1da36077c79 100644 --- a/x-pack/plugins/case/server/routes/api/index.ts +++ b/x-pack/plugins/case/server/routes/api/index.ts @@ -14,7 +14,10 @@ import { initPushCaseApi } from './cases/push_case'; import { initGetReportersApi } from './cases/reporters/get_reporters'; import { initGetCasesStatusApi } from './cases/status/get_status'; import { initGetTagsApi } from './cases/tags/get_tags'; -import { initGetAllUserActionsApi } from './cases/user_actions/get_all_user_actions'; +import { + initGetAllCaseUserActionsApi, + initGetAllSubCaseUserActionsApi, +} from './cases/user_actions/get_all_user_actions'; import { initDeleteCommentApi } from './cases/comments/delete_comment'; import { initDeleteAllCommentsApi } from './cases/comments/delete_all_comments'; @@ -52,7 +55,8 @@ export function initCaseApi(deps: RouteDeps) { initPatchCasesApi(deps); initPostCaseApi(deps); initPushCaseApi(deps); - initGetAllUserActionsApi(deps); + initGetAllCaseUserActionsApi(deps); + initGetAllSubCaseUserActionsApi(deps); // Sub cases initGetSubCaseApi(deps); initPatchSubCasesApi(deps); diff --git a/x-pack/plugins/case/server/routes/api/utils.test.ts b/x-pack/plugins/case/server/routes/api/utils.test.ts index 1efec927efb62f..f6bc1e4f718971 100644 --- a/x-pack/plugins/case/server/routes/api/utils.test.ts +++ b/x-pack/plugins/case/server/routes/api/utils.test.ts @@ -401,6 +401,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", @@ -440,6 +441,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "Data Destruction", @@ -483,6 +485,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -530,6 +533,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "closed", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -594,6 +598,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -649,6 +654,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -727,6 +733,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "LOLBins", @@ -781,6 +788,7 @@ describe('Utils', () => { "syncAlerts": true, }, "status": "open", + "subCaseIds": undefined, "subCases": undefined, "tags": Array [ "defacement", diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts index bc82f656f477b3..084b1a17a1434a 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { isEmpty } from 'lodash'; import { badRequest, boomify, isBoom } from '@hapi/boom'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; @@ -120,7 +121,8 @@ export interface AlertInfo { const accumulateIndicesAndIDs = (comment: CommentAttributes, acc: AlertInfo): AlertInfo => { if (isCommentRequestTypeAlertOrGenAlert(comment)) { acc.ids.push(...getAlertIds(comment)); - acc.indices.add(comment.index); + const indices = Array.isArray(comment.index) ? comment.index : [comment.index]; + indices.forEach((index) => acc.indices.add(index)); } return acc; }; @@ -249,12 +251,14 @@ export const flattenCaseSavedObject = ({ totalComment = comments.length, totalAlerts = 0, subCases, + subCaseIds, }: { savedObject: SavedObject; comments?: Array>; totalComment?: number; totalAlerts?: number; subCases?: SubCaseResponse[]; + subCaseIds?: string[]; }): CaseResponse => ({ id: savedObject.id, version: savedObject.version ?? '0', @@ -264,6 +268,7 @@ export const flattenCaseSavedObject = ({ ...savedObject.attributes, connector: transformESConnectorToCaseConnector(savedObject.attributes.connector), subCases, + subCaseIds: !isEmpty(subCaseIds) ? subCaseIds : undefined, }); export const flattenSubCaseSavedObject = ({ diff --git a/x-pack/plugins/case/server/saved_object_types/comments.ts b/x-pack/plugins/case/server/saved_object_types/comments.ts index 9eabf744f2e132..a4fdc24b6e4eec 100644 --- a/x-pack/plugins/case/server/saved_object_types/comments.ts +++ b/x-pack/plugins/case/server/saved_object_types/comments.ts @@ -63,6 +63,16 @@ export const caseCommentSavedObjectType: SavedObjectsType = { }, }, }, + rule: { + properties: { + id: { + type: 'keyword', + }, + name: { + type: 'keyword', + }, + }, + }, updated_at: { type: 'date', }, diff --git a/x-pack/plugins/case/server/saved_object_types/migrations.ts b/x-pack/plugins/case/server/saved_object_types/migrations.ts index a0b22c49d0bc6d..21ef27de1ec851 100644 --- a/x-pack/plugins/case/server/saved_object_types/migrations.ts +++ b/x-pack/plugins/case/server/saved_object_types/migrations.ts @@ -173,8 +173,9 @@ interface SanitizedComment { type: CommentType; } -interface SanitizedCommentAssociationType { +interface SanitizedCommentFoSubCases { associationType: AssociationType; + rule: { id: string | null; name: string | null }; } export const commentsMigrations = { @@ -192,11 +193,12 @@ export const commentsMigrations = { }, '7.12.0': ( doc: SavedObjectUnsanitizedDoc - ): SavedObjectSanitizedDoc => { + ): SavedObjectSanitizedDoc => { return { ...doc, attributes: { ...doc.attributes, + rule: { id: null, name: null }, associationType: AssociationType.case, }, references: doc.references || [], diff --git a/x-pack/plugins/case/server/scripts/sub_cases/index.ts b/x-pack/plugins/case/server/scripts/sub_cases/index.ts index 2ea9718d18487e..9dd577c40c74e6 100644 --- a/x-pack/plugins/case/server/scripts/sub_cases/index.ts +++ b/x-pack/plugins/case/server/scripts/sub_cases/index.ts @@ -16,6 +16,7 @@ import { import { CommentType } from '../../../common/api/cases/comment'; import { CASES_URL } from '../../../common/constants'; import { ActionResult, ActionTypeExecutorResult } from '../../../../actions/common'; +import { ContextTypeGeneratedAlertType, createAlertsString } from '../../connectors'; main(); @@ -105,6 +106,18 @@ async function handleGenGroupAlerts(argv: any) { } console.log('Case id: ', caseID); + const comment: ContextTypeGeneratedAlertType = { + type: CommentType.generatedAlert, + alerts: createAlertsString( + argv.ids.map((id: string) => ({ + _id: id, + _index: argv.signalsIndex, + ruleId: argv.ruleID, + ruleName: argv.ruleName, + })) + ), + }; + const executeResp = await client.request< ActionTypeExecutorResult >({ @@ -115,11 +128,7 @@ async function handleGenGroupAlerts(argv: any) { subAction: 'addComment', subActionParams: { caseId: caseID, - comment: { - type: CommentType.generatedAlert, - alerts: argv.ids.map((id: string) => ({ _id: id })), - index: argv.signalsIndex, - }, + comment, }, }, }, @@ -175,6 +184,18 @@ async function main() { type: 'string', default: '.siem-signals-default', }, + ruleID: { + alias: 'ri', + describe: 'siem signals rule id', + type: 'string', + default: 'rule-id', + }, + ruleName: { + alias: 'rn', + describe: 'siem signals rule name', + type: 'string', + default: 'rule-name', + }, }) .demandOption(['ids']); }, diff --git a/x-pack/plugins/case/server/services/alerts/index.ts b/x-pack/plugins/case/server/services/alerts/index.ts index 320d32ac0d7887..a19e533418bc9a 100644 --- a/x-pack/plugins/case/server/services/alerts/index.ts +++ b/x-pack/plugins/case/server/services/alerts/index.ts @@ -11,6 +11,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { ElasticsearchClient } from 'kibana/server'; import { CaseStatuses } from '../../../common/api'; +import { MAX_ALERTS_PER_SUB_CASE } from '../../../common/constants'; export type AlertServiceContract = PublicMethodsOf; @@ -95,14 +96,14 @@ export class AlertService { query: { bool: { filter: { - bool: { - should: ids.map((_id) => ({ match: { _id } })), - minimum_should_match: 1, + ids: { + values: ids, }, }, }, }, }, + size: MAX_ALERTS_PER_SUB_CASE, ignore_unavailable: true, }); diff --git a/x-pack/plugins/case/server/services/user_actions/index.ts b/x-pack/plugins/case/server/services/user_actions/index.ts index 091775827c6a61..d05ada0dba30cd 100644 --- a/x-pack/plugins/case/server/services/user_actions/index.ts +++ b/x-pack/plugins/case/server/services/user_actions/index.ts @@ -13,11 +13,16 @@ import { } from 'kibana/server'; import { CaseUserActionAttributes } from '../../../common/api'; -import { CASE_USER_ACTION_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../../saved_object_types'; +import { + CASE_USER_ACTION_SAVED_OBJECT, + CASE_SAVED_OBJECT, + SUB_CASE_SAVED_OBJECT, +} from '../../saved_object_types'; import { ClientArgs } from '..'; interface GetCaseUserActionArgs extends ClientArgs { caseId: string; + subCaseId?: string; } export interface UserActionItem { @@ -41,18 +46,20 @@ export interface CaseUserActionServiceSetup { export class CaseUserActionService { constructor(private readonly log: Logger) {} public setup = async (): Promise => ({ - getUserActions: async ({ client, caseId }: GetCaseUserActionArgs) => { + getUserActions: async ({ client, caseId, subCaseId }: GetCaseUserActionArgs) => { try { + const id = subCaseId ?? caseId; + const type = subCaseId ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT; const caseUserActionInfo = await client.find({ type: CASE_USER_ACTION_SAVED_OBJECT, fields: [], - hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, + hasReference: { type, id }, page: 1, perPage: 1, }); return await client.find({ type: CASE_USER_ACTION_SAVED_OBJECT, - hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, + hasReference: { type, id }, page: 1, perPage: caseUserActionInfo.total, sortField: 'action_at', diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index bc71df5d9e0084..31b4cef1a9d451 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -171,7 +171,7 @@ export const ML_GROUP_IDS = [ML_GROUP_ID, LEGACY_ML_GROUP_ID]; /* Rule notifications options */ -export const ENABLE_CASE_CONNECTOR = false; +export const ENABLE_CASE_CONNECTOR = true; export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [ '.email', '.slack', diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx index c447e00cbb94fc..d02f7e0ee09615 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx @@ -79,7 +79,12 @@ describe('AddComment ', () => { await waitFor(() => { expect(onCommentSaving).toBeCalled(); - expect(postComment).toBeCalledWith(addCommentProps.caseId, sampleData, onCommentPosted); + expect(postComment).toBeCalledWith({ + caseId: addCommentProps.caseId, + data: sampleData, + subCaseId: undefined, + updateCase: onCommentPosted, + }); expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe(''); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx index 01b86a989e022f..c94ef75523e2cc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx @@ -39,11 +39,15 @@ interface AddCommentProps { onCommentSaving?: () => void; onCommentPosted: (newCase: Case) => void; showLoading?: boolean; + subCaseId?: string; } export const AddComment = React.memo( forwardRef( - ({ caseId, disabled, showLoading = true, onCommentPosted, onCommentSaving }, ref) => { + ( + { caseId, disabled, onCommentPosted, onCommentSaving, showLoading = true, subCaseId }, + ref + ) => { const { isLoading, postComment } = usePostComment(); const { form } = useForm({ @@ -80,10 +84,15 @@ export const AddComment = React.memo( if (onCommentSaving != null) { onCommentSaving(); } - postComment(caseId, { ...data, type: CommentType.user }, onCommentPosted); + postComment({ + caseId, + data: { ...data, type: CommentType.user }, + updateCase: onCommentPosted, + subCaseId, + }); reset(); } - }, [onCommentPosted, onCommentSaving, postComment, reset, submit, caseId]); + }, [caseId, onCommentPosted, onCommentSaving, postComment, reset, submit, subCaseId]); return ( diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx index ae2d9879559931..8178e7e9f9e8f8 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx @@ -9,7 +9,7 @@ import { Dispatch } from 'react'; import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types'; import { CaseStatuses } from '../../../../../case/common/api'; -import { Case } from '../../containers/types'; +import { Case, SubCase } from '../../containers/types'; import { UpdateCase } from '../../containers/use_get_cases'; import * as i18n from './translations'; @@ -19,6 +19,9 @@ interface GetActions { deleteCaseOnClick: (deleteCase: Case) => void; } +const hasSubCases = (subCases: SubCase[] | null | undefined) => + subCases != null && subCases?.length > 0; + export const getActions = ({ caseStatus, dispatchUpdate, @@ -32,33 +35,34 @@ export const getActions = ({ type: 'icon', 'data-test-subj': 'action-delete', }, - caseStatus === CaseStatuses.open - ? { - description: i18n.CLOSE_CASE, - icon: 'folderCheck', - name: i18n.CLOSE_CASE, - onClick: (theCase: Case) => - dispatchUpdate({ - updateKey: 'status', - updateValue: CaseStatuses.closed, - caseId: theCase.id, - version: theCase.version, - }), - type: 'icon', - 'data-test-subj': 'action-close', - } - : { - description: i18n.REOPEN_CASE, - icon: 'folderExclamation', - name: i18n.REOPEN_CASE, - onClick: (theCase: Case) => - dispatchUpdate({ - updateKey: 'status', - updateValue: CaseStatuses.open, - caseId: theCase.id, - version: theCase.version, - }), - type: 'icon', - 'data-test-subj': 'action-open', - }, + { + available: (item) => caseStatus === CaseStatuses.open && !hasSubCases(item.subCases), + description: i18n.CLOSE_CASE, + icon: 'folderCheck', + name: i18n.CLOSE_CASE, + onClick: (theCase: Case) => + dispatchUpdate({ + updateKey: 'status', + updateValue: CaseStatuses.closed, + caseId: theCase.id, + version: theCase.version, + }), + type: 'icon', + 'data-test-subj': 'action-close', + }, + { + available: (item) => caseStatus !== CaseStatuses.open && !hasSubCases(item.subCases), + description: i18n.REOPEN_CASE, + icon: 'folderExclamation', + name: i18n.REOPEN_CASE, + onClick: (theCase: Case) => + dispatchUpdate({ + updateKey: 'status', + updateValue: CaseStatuses.open, + caseId: theCase.id, + version: theCase.version, + }), + type: 'icon', + 'data-test-subj': 'action-open', + }, ]; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx index 38f1b343670c8e..47db362c7b4bfe 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx @@ -14,17 +14,20 @@ import { EuiTableActionsColumnType, EuiTableComputedColumnType, EuiTableFieldDataColumnType, - HorizontalAlignment, } from '@elastic/eui'; +import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import styled from 'styled-components'; import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types'; import { CaseStatuses } from '../../../../../case/common/api'; import { getEmptyTagValue } from '../../../common/components/empty_value'; -import { Case } from '../../containers/types'; +import { Case, SubCase } from '../../containers/types'; import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; import { CaseDetailsLink } from '../../../common/components/links'; import * as i18n from './translations'; +import { Status } from '../status'; +import { getSubCasesStatusCountsBadges, isSubCase } from './helpers'; +import { ALERTS } from '../../../app/home/translations'; export type CasesColumns = | EuiTableFieldDataColumnType @@ -54,10 +57,14 @@ export const getCasesColumns = ( const columns = [ { name: i18n.NAME, - render: (theCase: Case) => { + render: (theCase: Case | SubCase) => { if (theCase.id != null && theCase.title != null) { const caseDetailsLinkComponent = !isModal ? ( - + {theCase.title} ) : ( @@ -122,7 +129,17 @@ export const getCasesColumns = ( truncateText: true, }, { - align: 'right' as HorizontalAlignment, + align: RIGHT_ALIGNMENT, + field: 'totalAlerts', + name: ALERTS, + sortable: true, + render: (totalAlerts: Case['totalAlerts']) => + totalAlerts != null + ? renderStringField(`${totalAlerts}`, `case-table-column-alertsCount`) + : getEmptyTagValue(), + }, + { + align: RIGHT_ALIGNMENT, field: 'totalComment', name: i18n.COMMENTS, sortable: true, @@ -183,6 +200,24 @@ export const getCasesColumns = ( return getEmptyTagValue(); }, }, + { + name: i18n.STATUS, + render: (theCase: Case) => { + if (theCase?.subCases == null || theCase.subCases.length === 0) { + if (theCase.status == null) { + return getEmptyTagValue(); + } + return ; + } + + const badges = getSubCasesStatusCountsBadges(theCase.subCases); + return badges.map(({ color, count }, index) => ( + + {count} + + )); + }, + }, { name: i18n.ACTIONS, actions, diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx new file mode 100644 index 00000000000000..bb4bd0f98949da --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx @@ -0,0 +1,68 @@ +/* + * 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 React from 'react'; +import { EuiBasicTable as _EuiBasicTable } from '@elastic/eui'; +import styled from 'styled-components'; +import { Case } from '../../containers/types'; +import { CasesColumns } from './columns'; +import { AssociationType } from '../../../../../case/common/api'; + +type ExpandedRowMap = Record | {}; + +const EuiBasicTable: any = _EuiBasicTable; // eslint-disable-line @typescript-eslint/no-explicit-any +const BasicTable = styled(EuiBasicTable)` + thead { + display: none; + } + + tbody { + .euiTableCellContent { + padding: 8px !important; + } + .euiTableRowCell { + border: 0; + } + } +`; +BasicTable.displayName = 'BasicTable'; + +export const getExpandedRowMap = ({ + data, + columns, +}: { + data: Case[] | null; + columns: CasesColumns[]; +}): ExpandedRowMap => { + if (data == null) { + return {}; + } + + return data.reduce((acc, curr) => { + if (curr.subCases != null) { + const subCases = curr.subCases.map((subCase, index) => ({ + ...subCase, + caseParentId: curr.id, + title: `${curr.title} ${index + 1}`, + associationType: AssociationType.subCase, + })); + return { + ...acc, + [curr.id]: ( + + ), + }; + } else { + return acc; + } + }, {}); +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts b/x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts new file mode 100644 index 00000000000000..1ab36d3c672250 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { filter } from 'lodash/fp'; +import { AssociationType, CaseStatuses } from '../../../../../case/common/api'; +import { Case, SubCase } from '../../containers/types'; +import { statuses } from '../status'; + +export const isSubCase = (theCase: Case | SubCase): theCase is SubCase => + (theCase as SubCase).caseParentId !== undefined && + (theCase as SubCase).associationType === AssociationType.subCase; + +export const getSubCasesStatusCountsBadges = ( + subCases: SubCase[] +): Array<{ name: CaseStatuses; color: string; count: number }> => { + return [ + { + name: CaseStatuses.open, + color: statuses[CaseStatuses.open].color, + count: filter({ status: CaseStatuses.open }, subCases).length, + }, + { + name: CaseStatuses['in-progress'], + color: statuses[CaseStatuses['in-progress']].color, + count: filter({ status: CaseStatuses['in-progress'] }, subCases).length, + }, + { + name: CaseStatuses.closed, + color: statuses[CaseStatuses.closed].color, + count: filter({ status: CaseStatuses.closed }, subCases).length, + }, + ]; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx index 318143426af584..a44ccd2384843c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx @@ -210,9 +210,12 @@ describe('AllCases', () => { id: null, createdAt: null, createdBy: null, + status: null, + subCases: null, tags: null, title: null, totalComment: null, + totalAlerts: null, }, ], }, @@ -542,6 +545,7 @@ describe('AllCases', () => { }, id: '1', status: 'open', + subCaseIds: [], tags: ['coke', 'pepsi'], title: 'Another horrible breach!!', totalAlerts: 0, diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx index 001251a8a71ae6..ce0fea07bf473c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { - EuiBasicTable, + EuiBasicTable as _EuiBasicTable, EuiContextMenuPanel, EuiEmptyPrompt, EuiFlexGroup, @@ -53,6 +53,7 @@ import { SecurityPageName } from '../../../app/types'; import { useKibana } from '../../../common/lib/kibana'; import { APP_ID } from '../../../../common/constants'; import { Stats } from '../status'; +import { getExpandedRowMap } from './expanded_row'; const Div = styled.div` margin-top: ${({ theme }) => theme.eui.paddingSizes.m}; @@ -83,6 +84,14 @@ const getSortField = (field: string): SortFieldCase => { return SortFieldCase.createdAt; }; +const EuiBasicTable: any = _EuiBasicTable; // eslint-disable-line @typescript-eslint/no-explicit-any +const BasicTable = styled(EuiBasicTable)` + .euiTableRow-isExpandedRow.euiTableRow-isSelectable .euiTableCellContent { + padding: 8px 0 8px 32px; + } +`; +BasicTable.displayName = 'BasicTable'; + interface AllCasesProps { onRowClick?: (theCase?: Case) => void; isModal?: boolean; @@ -130,7 +139,7 @@ export const AllCases = React.memo( isUpdated, updateBulkStatus, } = useUpdateCases(); - const [deleteThisCase, setDeleteThisCase] = useState({ + const [deleteThisCase, setDeleteThisCase] = useState({ title: '', id: '', }); @@ -190,7 +199,7 @@ export const AllCases = React.memo( const toggleDeleteModal = useCallback( (deleteCase: Case) => { handleToggleModal(); - setDeleteThisCase(deleteCase); + setDeleteThisCase({ id: deleteCase.id, title: deleteCase.title, type: deleteCase.type }); }, [handleToggleModal] ); @@ -201,7 +210,11 @@ export const AllCases = React.memo( if (caseIds.length === 1) { const singleCase = selectedCases.find((theCase) => theCase.id === caseIds[0]); if (singleCase) { - return setDeleteThisCase({ id: singleCase.id, title: singleCase.title }); + return setDeleteThisCase({ + id: singleCase.id, + title: singleCase.title, + type: singleCase.type, + }); } } const convertToDeleteCases: DeleteCase[] = caseIds.map((id) => ({ id })); @@ -315,6 +328,16 @@ export const AllCases = React.memo( () => getCasesColumns(userCanCrud ? actions : [], filterOptions.status, isModal), [actions, filterOptions.status, userCanCrud, isModal] ); + + const itemIdToExpandedRowMap = useMemo( + () => + getExpandedRowMap({ + columns: memoizedGetCasesColumns, + data: data.cases, + }), + [data.cases, memoizedGetCasesColumns] + ); + const memoizedPagination = useMemo( () => ({ pageIndex: queryParams.page - 1, @@ -330,7 +353,10 @@ export const AllCases = React.memo( }; const euiBasicTableSelectionProps = useMemo>( - () => ({ onSelectionChange: setSelectedCases }), + () => ({ + selectable: (theCase) => isEmpty(theCase.subCases), + onSelectionChange: setSelectedCases, + }), [setSelectedCases] ); const isCasesLoading = useMemo( @@ -472,12 +498,13 @@ export const AllCases = React.memo( )} - {i18n.NO_CASES}} diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts b/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts index bcc5df6c39e5bf..3b27ef25eda146 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts @@ -103,3 +103,7 @@ export const SERVICENOW_LINK_ARIA = i18n.translate( defaultMessage: 'click to view the incident on servicenow', } ); + +export const STATUS = i18n.translate('xpack.securitySolution.case.caseTable.status', { + defaultMessage: 'Status', +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx index a1ee825aa5337e..70e6636cc737f3 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx @@ -8,7 +8,7 @@ import { AssociationType, CommentType } from '../../../../../case/common/api'; import { Comment } from '../../containers/types'; -import { getRuleIdsFromComments, buildAlertsQuery } from './helpers'; +import { getManualAlertIdsWithNoRuleId, buildAlertsQuery } from './helpers'; const comments: Comment[] = [ { @@ -19,6 +19,10 @@ const comments: Comment[] = [ id: 'comment-id', createdAt: '2020-02-19T23:06:33.798Z', createdBy: { username: 'elastic' }, + rule: { + id: null, + name: null, + }, pushedAt: null, pushedBy: null, updatedAt: null, @@ -35,6 +39,10 @@ const comments: Comment[] = [ createdBy: { username: 'elastic' }, pushedAt: null, pushedBy: null, + rule: { + id: 'rule-id-2', + name: 'rule-name-2', + }, updatedAt: null, updatedBy: null, version: 'WzQ3LDFc', @@ -42,9 +50,9 @@ const comments: Comment[] = [ ]; describe('Case view helpers', () => { - describe('getRuleIdsFromComments', () => { - it('it returns the rules ids from the comments', () => { - expect(getRuleIdsFromComments(comments)).toEqual(['alert-id-1', 'alert-id-2']); + describe('getAlertIdsFromComments', () => { + it('it returns the alert id from the comments where rule is not defined', () => { + expect(getManualAlertIdsWithNoRuleId(comments)).toEqual(['alert-id-1']); }); }); @@ -54,13 +62,13 @@ describe('Case view helpers', () => { query: { bool: { filter: { - bool: { - should: [{ match: { _id: 'alert-id-1' } }, { match: { _id: 'alert-id-2' } }], - minimum_should_match: 1, + ids: { + values: ['alert-id-1', 'alert-id-2'], }, }, }, }, + size: 10000, }); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts index 6b92e414675e2c..3dece29e64ac5e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts @@ -5,28 +5,37 @@ * 2.0. */ +import { isEmpty } from 'lodash'; import { CommentType } from '../../../../../case/common/api'; import { Comment } from '../../containers/types'; -export const getRuleIdsFromComments = (comments: Comment[]) => - comments.reduce((ruleIds, comment: Comment) => { - if (comment.type === CommentType.alert) { +export const getManualAlertIdsWithNoRuleId = (comments: Comment[]): string[] => { + const dedupeAlerts = comments.reduce((alertIds, comment: Comment) => { + if (comment.type === CommentType.alert && isEmpty(comment.rule.id)) { const ids = Array.isArray(comment.alertId) ? comment.alertId : [comment.alertId]; - return [...ruleIds, ...ids]; + ids.forEach((id) => alertIds.add(id)); + return alertIds; } + return alertIds; + }, new Set()); + return [...dedupeAlerts]; +}; - return ruleIds; - }, []); - -export const buildAlertsQuery = (ruleIds: string[]) => ({ - query: { - bool: { - filter: { - bool: { - should: ruleIds.map((_id) => ({ match: { _id } })), - minimum_should_match: 1, +// TODO we need to allow -> docValueFields: [{ field: "@timestamp" }], +export const buildAlertsQuery = (alertIds: string[]) => { + if (alertIds.length === 0) { + return {}; + } + return { + query: { + bool: { + filter: { + ids: { + values: alertIds, + }, }, }, }, - }, -}); + size: 10000, + }; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx index dc0ef9ad026a41..7a5f6647a8dcf6 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx @@ -629,6 +629,14 @@ describe('CaseView ', () => { loading: true, data: { hits: { hits: [] } }, })); + useGetCaseUserActionsMock.mockReturnValue({ + caseServices: {}, + caseUserActions: [], + hasDataToPush: false, + isError: false, + isLoading: true, + participants: [], + }); const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 0eaa867077a4a7..e42431e55ee290 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -42,8 +42,6 @@ import { normalizeActionConnector, getNoneConnector, } from '../configure_cases/utils'; -import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; -import { buildAlertsQuery, getRuleIdsFromComments } from './helpers'; import { DetailsPanel } from '../../../timelines/components/side_panel'; import { useSourcererScope } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; @@ -55,6 +53,7 @@ import * as i18n from './translations'; interface Props { caseId: string; + subCaseId?: string; userCanCrud: boolean; } @@ -87,32 +86,8 @@ export interface CaseProps extends Props { updateCase: (newCase: Case) => void; } -interface Signal { - rule: { - id: string; - name: string; - to: string; - from: string; - }; -} - -interface SignalHit { - _id: string; - _index: string; - _source: { - '@timestamp': string; - signal: Signal; - }; -} - -export type Alert = { - _id: string; - _index: string; - '@timestamp': string; -} & Signal; - export const CaseComponent = React.memo( - ({ caseId, caseData, fetchCase, updateCase, userCanCrud }) => { + ({ caseId, caseData, fetchCase, subCaseId, updateCase, userCanCrud }) => { const dispatch = useDispatch(); const { formatUrl, search } = useFormatUrl(SecurityPageName.case); const allCasesLink = getCaseUrl(search); @@ -127,45 +102,18 @@ export const CaseComponent = React.memo( hasDataToPush, isLoading: isLoadingUserActions, participants, - } = useGetCaseUserActions(caseId, caseData.connector.id); + } = useGetCaseUserActions(caseId, caseData.connector.id, subCaseId); const { isLoading, updateKey, updateCaseProperty } = useUpdateCase({ caseId, + subCaseId, }); - const alertsQuery = useMemo(() => buildAlertsQuery(getRuleIdsFromComments(caseData.comments)), [ - caseData.comments, - ]); - /** * For the future developer: useSourcererScope is security solution dependent. * You can use useSignalIndex as an alternative. */ - const { browserFields, docValueFields, selectedPatterns } = useSourcererScope( - SourcererScopeName.detections - ); - - const { loading: isLoadingAlerts, data: alertsData } = useQueryAlerts( - alertsQuery, - selectedPatterns[0] - ); - - const alerts = useMemo( - () => - alertsData?.hits.hits.reduce>( - (acc, { _id, _index, _source }) => ({ - ...acc, - [_id]: { - _id, - _index, - '@timestamp': _source['@timestamp'], - ..._source.signal, - }, - }), - {} - ) ?? {}, - [alertsData?.hits.hits] - ); + const { browserFields, docValueFields } = useSourcererScope(SourcererScopeName.detections); // Update Fields const onUpdateField = useCallback( @@ -350,10 +298,10 @@ export const CaseComponent = React.memo( ); useEffect(() => { - if (initLoadingData && !isLoadingUserActions && !isLoadingAlerts) { + if (initLoadingData && !isLoadingUserActions) { setInitLoadingData(false); } - }, [initLoadingData, isLoadingAlerts, isLoadingUserActions]); + }, [initLoadingData, isLoadingUserActions]); const backOptions = useMemo( () => ({ @@ -435,18 +383,17 @@ export const CaseComponent = React.memo( {!initLoadingData && ( <> @@ -513,8 +460,8 @@ export const CaseComponent = React.memo( } ); -export const CaseView = React.memo(({ caseId, userCanCrud }: Props) => { - const { data, isLoading, isError, fetchCase, updateCase } = useGetCase(caseId); +export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) => { + const { data, isLoading, isError, fetchCase, updateCase } = useGetCase(caseId, subCaseId); if (isError) { return null; } @@ -531,6 +478,7 @@ export const CaseView = React.memo(({ caseId, userCanCrud }: Props) => { return ( void; @@ -36,7 +36,13 @@ const ConfirmDeleteCaseModalComp: React.FC = ({ defaultFocusedButton="confirm" onCancel={onCancel} onConfirm={onConfirm} - title={isPlural ? i18n.DELETE_SELECTED_CASES : i18n.DELETE_TITLE(caseTitle)} + title={ + isPlural + ? i18n.DELETE_SELECTED_CASES + : caseTitle == null + ? i18n.DELETE_THIS_CASE + : i18n.DELETE_TITLE(caseTitle) + } > {isPlural ? i18n.CONFIRM_QUESTION_PLURAL : i18n.CONFIRM_QUESTION} diff --git a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts index 2c2ebc53607994..0bd37fa18281a6 100644 --- a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts @@ -14,6 +14,11 @@ export const DELETE_TITLE = (caseTitle: string) => defaultMessage: 'Delete "{caseTitle}"', }); +export const DELETE_THIS_CASE = (caseTitle: string) => + i18n.translate('xpack.securitySolution.case.confirmDeleteCase.deleteThisCase', { + defaultMessage: 'Delete this case', + }); + export const CONFIRM_QUESTION = i18n.translate( 'xpack.securitySolution.case.confirmDeleteCase.confirmQuestion', { diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx index 656257f2b36c43..d5c90bd09a6db2 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx @@ -27,9 +27,7 @@ const Container = styled.div` const defaultAlertComment = { type: CommentType.generatedAlert, - alerts: '{{context.alerts}}', - index: '{{context.rule.output_index}}', - ruleId: '{{context.rule.id}}', + alerts: `[{{#context.alerts}}{"_id": "{{_id}}", "_index": "{{_index}}", "ruleId": "{{rule.id}}", "ruleName": "{{rule.name}}"}__SEPARATOR__{{/context.alerts}}]`, }; const CaseParamsFields: React.FunctionComponent> = ({ diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx index 348e488a36a8d6..1c786bade97533 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx @@ -5,10 +5,22 @@ * 2.0. */ -import React, { memo, useMemo, useCallback } from 'react'; -import { useGetCases } from '../../../containers/use_get_cases'; +import { + EuiButton, + EuiButtonIcon, + EuiCallOut, + EuiTextColor, + EuiLoadingSpinner, +} from '@elastic/eui'; +import { isEmpty } from 'lodash'; +import React, { memo, useEffect, useCallback, useState } from 'react'; +import { CaseType } from '../../../../../../case/common/api'; +import { Case } from '../../../containers/types'; +import { useDeleteCases } from '../../../containers/use_delete_cases'; +import { useGetCase } from '../../../containers/use_get_case'; +import { ConfirmDeleteCaseModal } from '../../confirm_delete_case'; import { useCreateCaseModal } from '../../use_create_case_modal'; -import { CasesDropdown, ADD_CASE_BUTTON_ID } from './cases_dropdown'; +import * as i18n from './translations'; interface ExistingCaseProps { selectedCase: string | null; @@ -16,37 +28,72 @@ interface ExistingCaseProps { } const ExistingCaseComponent: React.FC = ({ onCaseChanged, selectedCase }) => { - const { data: cases, loading: isLoadingCases, refetchCases } = useGetCases(); + const { data, isLoading, isError } = useGetCase(selectedCase ?? ''); + const [createdCase, setCreatedCase] = useState(null); - const onCaseCreated = useCallback(() => refetchCases(), [refetchCases]); + const onCaseCreated = useCallback( + (newCase: Case) => { + onCaseChanged(newCase.id); + setCreatedCase(newCase); + }, + [onCaseChanged] + ); - const { modal, openModal } = useCreateCaseModal({ onCaseCreated }); + const { modal, openModal } = useCreateCaseModal({ caseType: CaseType.collection, onCaseCreated }); - const onChange = useCallback( - (id: string) => { - if (id === ADD_CASE_BUTTON_ID) { - openModal(); - return; - } + // Delete case + const { + dispatchResetIsDeleted, + handleOnDeleteConfirm, + handleToggleModal, + isLoading: isDeleting, + isDeleted, + isDisplayConfirmDeleteModal, + } = useDeleteCases(); - onCaseChanged(id); - }, - [onCaseChanged, openModal] - ); + useEffect(() => { + if (isDeleted) { + setCreatedCase(null); + onCaseChanged(''); + dispatchResetIsDeleted(); + } + }, [isDeleted, dispatchResetIsDeleted, onCaseChanged]); - const isCasesLoading = useMemo( - () => isLoadingCases.includes('cases') || isLoadingCases.includes('caseUpdate'), - [isLoadingCases] - ); + useEffect(() => { + if (!isLoading && !isError && data != null) { + setCreatedCase(data); + onCaseChanged(data.id); + } + }, [data, isLoading, isError, onCaseChanged]); return ( <> - + {createdCase == null && isEmpty(selectedCase) && ( + + {i18n.CREATE_CASE} + + )} + {createdCase == null && isLoading && } + {createdCase != null && !isLoading && ( + <> + + + {createdCase.title}{' '} + {!isDeleting && ( + + )} + {isDeleting && } + + + + + )} {modal} ); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts index c9553455f687da..731e94a17d9235 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts @@ -85,3 +85,17 @@ export const CASE_CONNECTOR_ADD_NEW_CASE = i18n.translate( defaultMessage: 'Add new case', } ); + +export const CREATE_CASE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.createCaseLabel', + { + defaultMessage: 'Create case', + } +); + +export const CONNECTED_CASE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.connectedCaseLabel', + { + defaultMessage: 'Connected case', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx index cc38e07cf49e4a..83b8870ab597d3 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx @@ -19,6 +19,7 @@ import { usePostPushToService } from '../../containers/use_post_push_to_service' import { useConnectors } from '../../containers/configure/use_connectors'; import { useCaseConfigure } from '../../containers/configure/use_configure'; import { Case } from '../../containers/types'; +import { CaseType } from '../../../../../case/common/api'; const initialCaseValue: FormProps = { description: '', @@ -30,10 +31,15 @@ const initialCaseValue: FormProps = { }; interface Props { + caseType?: CaseType; onSuccess?: (theCase: Case) => void; } -export const FormContext: React.FC = ({ children, onSuccess }) => { +export const FormContext: React.FC = ({ + caseType = CaseType.individual, + children, + onSuccess, +}) => { const { connectors } = useConnectors(); const { connector: configurationConnector } = useCaseConfigure(); const { postCase } = usePostCase(); @@ -61,6 +67,7 @@ export const FormContext: React.FC = ({ children, onSuccess }) => { const updatedCase = await postCase({ ...dataWithoutConnectorId, + type: caseType, connector: connectorToUpdate, settings: { syncAlerts }, }); @@ -77,7 +84,7 @@ export const FormContext: React.FC = ({ children, onSuccess }) => { } } }, - [connectors, postCase, onSuccess, pushCaseToExternalService] + [caseType, connectors, postCase, onSuccess, pushCaseToExternalService] ); const { form } = useForm({ diff --git a/x-pack/plugins/security_solution/public/cases/components/create/mock.ts b/x-pack/plugins/security_solution/public/cases/components/create/mock.ts index 909b49940e1899..81a7fe9cd9387b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/components/create/mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CasePostRequest } from '../../../../../case/common/api'; +import { CasePostRequest, CaseType } from '../../../../../case/common/api'; import { ConnectorTypes } from '../../../../../case/common/api/connectors'; import { choices } from '../connectors/mock'; @@ -14,6 +14,7 @@ export const sampleData: CasePostRequest = { description: 'what a great description', tags: sampleTags, title: 'what a cool title', + type: CaseType.individual, connector: { fields: null, id: 'none', diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx index dc331e81c62d5d..aa1305f1f655cb 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx @@ -163,10 +163,14 @@ describe('AddToCaseAction', () => { wrapper.find(`[data-test-subj="form-context-on-success"]`).first().simulate('click'); - expect(postComment.mock.calls[0][0]).toBe('new-case'); - expect(postComment.mock.calls[0][1]).toEqual({ + expect(postComment.mock.calls[0][0].caseId).toBe('new-case'); + expect(postComment.mock.calls[0][0].data).toEqual({ alertId: 'test-id', index: 'test-index', + rule: { + id: null, + name: null, + }, type: 'alert', }); }); @@ -196,10 +200,14 @@ describe('AddToCaseAction', () => { wrapper.find(`[data-test-subj="all-cases-modal-button"]`).first().simulate('click'); - expect(postComment.mock.calls[0][0]).toBe('selected-case'); - expect(postComment.mock.calls[0][1]).toEqual({ + expect(postComment.mock.calls[0][0].caseId).toBe('selected-case'); + expect(postComment.mock.calls[0][0].data).toEqual({ alertId: 'test-id', index: 'test-index', + rule: { + id: null, + name: null, + }, type: 'alert', }); }); @@ -208,7 +216,7 @@ describe('AddToCaseAction', () => { usePostCommentMock.mockImplementation(() => { return { ...defaultPostComment, - postComment: jest.fn().mockImplementation((caseId, data, updateCase) => updateCase()), + postComment: jest.fn().mockImplementation(({ caseId, data, updateCase }) => updateCase()), }; }); diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx index 1b21db0491565f..aa9cec2d6b5b14 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx @@ -44,6 +44,7 @@ const AddToCaseActionComponent: React.FC = ({ }) => { const eventId = ecsRowData._id; const eventIndex = ecsRowData._index; + const rule = ecsRowData.signal?.rule; const { navigateToApp } = useKibana().services.application; const [, dispatchToaster] = useStateToaster(); @@ -71,21 +72,25 @@ const AddToCaseActionComponent: React.FC = ({ const attachAlertToCase = useCallback( (theCase: Case) => { closeCaseFlyoutOpen(); - postComment( - theCase.id, - { + postComment({ + caseId: theCase.id, + data: { type: CommentType.alert, alertId: eventId, index: eventIndex ?? '', + rule: { + id: rule?.id != null ? rule.id[0] : null, + name: rule?.name != null ? rule.name[0] : null, + }, }, - () => + updateCase: () => dispatchToaster({ type: 'addToaster', toast: createUpdateSuccessToaster(theCase, onViewCaseClick), - }) - ); + }), + }); }, - [closeCaseFlyoutOpen, postComment, eventId, eventIndex, dispatchToaster, onViewCaseClick] + [closeCaseFlyoutOpen, postComment, eventId, eventIndex, rule, dispatchToaster, onViewCaseClick] ); const onCaseClicked = useCallback( diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx index 8dd5080666cb38..2806e358fceee9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx @@ -14,11 +14,13 @@ import { CreateCaseForm } from '../create/form'; import { SubmitCaseButton } from '../create/submit_button'; import { Case } from '../../containers/types'; import * as i18n from '../../translations'; +import { CaseType } from '../../../../../case/common/api'; export interface CreateCaseModalProps { isModalOpen: boolean; onCloseCaseModal: () => void; onSuccess: (theCase: Case) => void; + caseType?: CaseType; } const Container = styled.div` @@ -32,6 +34,7 @@ const CreateModalComponent: React.FC = ({ isModalOpen, onCloseCaseModal, onSuccess, + caseType = CaseType.individual, }) => { return isModalOpen ? ( @@ -39,7 +42,7 @@ const CreateModalComponent: React.FC = ({ {i18n.CREATE_TITLE} - + diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx index 86313ebcb3bfad..3dc852a19e73fc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx @@ -6,11 +6,13 @@ */ import React, { useState, useCallback, useMemo } from 'react'; +import { CaseType } from '../../../../../case/common/api'; import { Case } from '../../containers/types'; import { CreateCaseModal } from './create_case_modal'; export interface UseCreateCaseModalProps { onCaseCreated: (theCase: Case) => void; + caseType?: CaseType; } export interface UseCreateCaseModalReturnedValues { modal: JSX.Element; @@ -19,7 +21,10 @@ export interface UseCreateCaseModalReturnedValues { openModal: () => void; } -export const useCreateCaseModal = ({ onCaseCreated }: UseCreateCaseModalProps) => { +export const useCreateCaseModal = ({ + caseType = CaseType.individual, + onCaseCreated, +}: UseCreateCaseModalProps) => { const [isModalOpen, setIsModalOpen] = useState(false); const closeModal = useCallback(() => setIsModalOpen(false), []); const openModal = useCallback(() => setIsModalOpen(true), []); @@ -35,6 +40,7 @@ export const useCreateCaseModal = ({ onCaseCreated }: UseCreateCaseModalProps) = () => ({ modal: ( void; + alertId: string; + index: string; + loadingAlertData: boolean; + ruleId: string; + ruleName: string; }): EuiCommentProps => { return { username: ( @@ -212,7 +231,15 @@ export const getAlertComment = ({ ), className: 'comment-alert', type: 'update', - event: , + event: ( + + ), 'data-test-subj': `${action.actionField[0]}-${action.action}-action-${action.actionId}`, timestamp: , timelineIcon: 'bell', @@ -222,23 +249,188 @@ export const getAlertComment = ({ - {alert != null ? ( - - ) : ( - - )} + ), }; }; + +export const toStringArray = (value: unknown): string[] => { + if (Array.isArray(value)) { + return value.reduce((acc, v) => { + if (v != null) { + switch (typeof v) { + case 'number': + case 'boolean': + return [...acc, v.toString()]; + case 'object': + try { + return [...acc, JSON.stringify(v)]; + } catch { + return [...acc, 'Invalid Object']; + } + case 'string': + return [...acc, v]; + default: + return [...acc, `${v}`]; + } + } + return acc; + }, []); + } else if (value == null) { + return []; + } else if (!Array.isArray(value) && typeof value === 'object') { + try { + return [JSON.stringify(value)]; + } catch { + return ['Invalid Object']; + } + } else { + return [`${value}`]; + } +}; + +export const formatAlertToEcsSignal = (alert: {}): Ecs => + Object.keys(alert).reduce((accumulator, key) => { + const item = get(alert, key); + if (item != null && isObject(item)) { + return { ...accumulator, [key]: formatAlertToEcsSignal(item) }; + } else if (Array.isArray(item) || isString(item) || isNumber(item)) { + return { ...accumulator, [key]: toStringArray(item) }; + } + return accumulator; + }, {} as Ecs); + +const EMPTY_ARRAY: TimelineNonEcsData[] = []; +export const getGeneratedAlertsAttachment = ({ + action, + alertIds, + ruleId, + ruleName, +}: { + action: CaseUserActions; + alertIds: string[]; + ruleId: string; + ruleName: string; +}): EuiCommentProps => { + const fetchEcsAlertsData = async (fetchAlertIds?: string[]): Promise => { + if (isEmpty(fetchAlertIds)) { + return []; + } + const alertResponse = await KibanaServices.get().http.fetch< + SearchResponse<{ '@timestamp': string; [key: string]: unknown }> + >(DETECTION_ENGINE_QUERY_SIGNALS_URL, { + method: 'POST', + body: JSON.stringify(buildAlertsQuery(fetchAlertIds ?? [])), + }); + return ( + alertResponse?.hits.hits.reduce( + (acc, { _id, _index, _source }) => [ + ...acc, + { + ...formatAlertToEcsSignal(_source as {}), + _id, + _index, + timestamp: _source['@timestamp'], + }, + ], + [] + ) ?? [] + ); + }; + return { + username: , + className: 'comment-alert', + type: 'update', + event: ( + + ), + 'data-test-subj': `${action.actionField[0]}-${action.action}-action-${action.actionId}`, + timestamp: , + timelineIcon: 'bell', + actions: ( + + + + + + + + + ), + }; +}; + +interface Signal { + rule: { + id: string; + name: string; + to: string; + from: string; + }; +} + +interface SignalHit { + _id: string; + _index: string; + _source: { + '@timestamp': string; + signal: Signal; + }; +} + +export interface Alert { + _id: string; + _index: string; + '@timestamp': string; + signal: Signal; + [key: string]: unknown; +} + +export const useFetchAlertData = (alertIds: string[]): [boolean, Record] => { + const { selectedPatterns } = useSourcererScope(SourcererScopeName.detections); + const alertsQuery = useMemo(() => buildAlertsQuery(alertIds), [alertIds]); + + const { loading: isLoadingAlerts, data: alertsData } = useQueryAlerts( + alertsQuery, + selectedPatterns[0] + ); + + const alerts = useMemo( + () => + alertsData?.hits.hits.reduce>( + (acc, { _id, _index, _source }) => ({ + ...acc, + [_id]: { + ...formatAlertToEcsSignal(_source), + _id, + _index, + timestamp: _source['@timestamp'], + }, + }), + {} + ) ?? {}, + [alertsData?.hits.hits] + ); + + return [isLoadingAlerts, alerts]; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx index 3b81fc0afccf3b..2a9f99465251b9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx @@ -5,8 +5,6 @@ * 2.0. */ -import classNames from 'classnames'; - import { EuiFlexGroup, EuiFlexItem, @@ -14,9 +12,12 @@ import { EuiCommentList, EuiCommentProps, } from '@elastic/eui'; +import classNames from 'classnames'; +import { isEmpty } from 'lodash'; import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; +import { isRight } from 'fp-ts/Either'; import * as i18n from './translations'; @@ -24,23 +25,31 @@ import { Case, CaseUserActions } from '../../containers/types'; import { useUpdateComment } from '../../containers/use_update_comment'; import { useCurrentUser } from '../../../common/lib/kibana'; import { AddComment, AddCommentRefObject } from '../add_comment'; -import { ActionConnector, CommentType } from '../../../../../case/common/api'; +import { + ActionConnector, + AlertCommentRequestRt, + CommentType, + ContextTypeUserRt, +} from '../../../../../case/common/api'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { parseString } from '../../containers/utils'; -import { Alert, OnUpdateFields } from '../case_view'; +import { OnUpdateFields } from '../case_view'; import { getConnectorLabelTitle, getLabelTitle, getPushedServiceLabelTitle, getPushInfo, getUpdateAction, - getAlertComment, + getAlertAttachment, + getGeneratedAlertsAttachment, + useFetchAlertData, } from './helpers'; import { UserActionAvatar } from './user_action_avatar'; import { UserActionMarkdown } from './user_action_markdown'; import { UserActionTimestamp } from './user_action_timestamp'; import { UserActionUsername } from './user_action_username'; import { UserActionContentToolbar } from './user_action_content_toolbar'; +import { getManualAlertIdsWithNoRuleId } from '../case_view/helpers'; export interface UserActionTreeProps { caseServices: CaseServices; @@ -53,7 +62,6 @@ export interface UserActionTreeProps { onUpdateField: ({ key, value, onSuccess, onError }: OnUpdateFields) => void; updateCase: (newCase: Case) => void; userCanCrud: boolean; - alerts: Record; onShowAlertDetails: (alertId: string, index: string) => void; } @@ -112,10 +120,9 @@ export const UserActionTree = React.memo( onUpdateField, updateCase, userCanCrud, - alerts, onShowAlertDetails, }: UserActionTreeProps) => { - const { commentId } = useParams<{ commentId?: string }>(); + const { commentId, subCaseId } = useParams<{ commentId?: string; subCaseId?: string }>(); const handlerTimeoutId = useRef(0); const addCommentRef = useRef(null); const [initLoading, setInitLoading] = useState(true); @@ -124,6 +131,10 @@ export const UserActionTree = React.memo( const currentUser = useCurrentUser(); const [manageMarkdownEditIds, setManangeMardownEditIds] = useState([]); + const [loadingAlertData, manualAlertsData] = useFetchAlertData( + getManualAlertIdsWithNoRuleId(caseData.comments) + ); + const handleManageMarkdownEditId = useCallback( (id: string) => { if (!manageMarkdownEditIds.includes(id)) { @@ -218,9 +229,10 @@ export const UserActionTree = React.memo( onCommentPosted={handleUpdate} onCommentSaving={handleManageMarkdownEditId.bind(null, NEW_ID)} showLoading={false} + subCaseId={subCaseId} /> ), - [caseData.id, handleUpdate, userCanCrud, handleManageMarkdownEditId] + [caseData.id, handleUpdate, userCanCrud, handleManageMarkdownEditId, subCaseId] ); useEffect(() => { @@ -279,11 +291,16 @@ export const UserActionTree = React.memo( const userActions: EuiCommentProps[] = useMemo( () => caseUserActions.reduce( + // eslint-disable-next-line complexity (comments, action, index) => { // Comment creation if (action.commentId != null && action.action === 'create') { const comment = caseData.comments.find((c) => c.id === action.commentId); - if (comment != null && comment.type === CommentType.user) { + if ( + comment != null && + isRight(ContextTypeUserRt.decode(comment)) && + comment.type === CommentType.user + ) { return [ ...comments, { @@ -335,16 +352,65 @@ export const UserActionTree = React.memo( ), }, ]; - // TODO: need to handle CommentType.generatedAlert here to - } else if (comment != null && comment.type === CommentType.alert) { + } else if ( + comment != null && + isRight(AlertCommentRequestRt.decode(comment)) && + comment.type === CommentType.alert + ) { // TODO: clean this up const alertId = Array.isArray(comment.alertId) ? comment.alertId.length > 0 ? comment.alertId[0] : '' : comment.alertId; - const alert = alerts[alertId]; - return [...comments, getAlertComment({ action, alert, onShowAlertDetails })]; + + const alertIndex = Array.isArray(comment.index) + ? comment.index.length > 0 + ? comment.index[0] + : '' + : comment.index; + + if (isEmpty(alertId)) { + return comments; + } + + const ruleId = comment?.rule?.id ?? manualAlertsData[alertId]?.rule?.id?.[0] ?? ''; + const ruleName = + comment?.rule?.name ?? + manualAlertsData[alertId]?.rule?.name?.[0] ?? + i18n.UNKNOWN_RULE; + + return [ + ...comments, + getAlertAttachment({ + action, + alertId, + index: alertIndex, + loadingAlertData, + ruleId, + ruleName, + onShowAlertDetails, + }), + ]; + } else if (comment != null && comment.type === CommentType.generatedAlert) { + // TODO: clean this up + const alertIds = Array.isArray(comment.alertId) + ? comment.alertId + : [comment.alertId]; + + if (isEmpty(alertIds)) { + return comments; + } + + return [ + ...comments, + getGeneratedAlertsAttachment({ + action, + alertIds, + ruleId: comment.rule?.id ?? '', + ruleName: comment.rule?.name ?? i18n.UNKNOWN_RULE, + }), + ]; } } @@ -438,10 +504,11 @@ export const UserActionTree = React.memo( handleManageQuote, handleSaveComment, isLoadingIds, + loadingAlertData, + manualAlertsData, manageMarkdownEditIds, selectedOutlineCommentId, userCanCrud, - alerts, onShowAlertDetails, ] ); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts index ede216d562f117..46f36615b1a4ea 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts @@ -42,6 +42,19 @@ export const ALERT_COMMENT_LABEL_TITLE = i18n.translate( } ); +export const GENERATED_ALERT_COMMENT_LABEL_TITLE = i18n.translate( + 'xpack.securitySolution.case.caseView.generatedAlertCommentLabelTitle', + { + defaultMessage: 'were added from', + } +); + +export const GENERATED_ALERT_COUNT_COMMENT_LABEL_TITLE = (totalCount: number) => + i18n.translate('xpack.securitySolution.case.caseView.generatedAlertCountCommentLabelTitle', { + values: { totalCount }, + defaultMessage: `{totalCount} {totalCount, plural, =1 {alert} other {alerts}}`, + }); + export const ALERT_RULE_DELETED_COMMENT_LABEL = i18n.translate( 'xpack.securitySolution.case.caseView.alertRuleDeletedLabelTitle', { @@ -56,9 +69,16 @@ export const SHOW_ALERT_TOOLTIP = i18n.translate( } ); -export const ALERT_NOT_FOUND_TOOLTIP = i18n.translate( - 'xpack.securitySolution.case.caseView.showAlertDeletedTooltip', +export const SEND_ALERT_TO_TIMELINE = i18n.translate( + 'xpack.securitySolution.case.caseView.sendAlertToTimelineTooltip', + { + defaultMessage: 'Investigate in timeline', + } +); + +export const UNKNOWN_RULE = i18n.translate( + 'xpack.securitySolution.case.caseView.unknownRule.label', { - defaultMessage: 'Alert not found', + defaultMessage: 'Unknown rule', } ); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx index f0ff145a269ff4..228945bacf8a49 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx @@ -11,19 +11,14 @@ import { mount } from 'enzyme'; import { TestProviders } from '../../../common/mock'; import { useKibana } from '../../../common/lib/kibana'; import { AlertCommentEvent } from './user_action_alert_comment_event'; +import { CommentType } from '../../../../../case/common/api'; const props = { - alert: { - _id: 'alert-id-1', - _index: 'alert-index-1', - '@timestamp': '2021-01-07T13:58:31.487Z', - rule: { - id: 'rule-id-1', - name: 'Awesome rule', - from: '2021-01-07T13:58:31.487Z', - to: '2021-01-07T14:58:31.487Z', - }, - }, + alertId: 'alert-id-1', + ruleId: 'rule-id-1', + ruleName: 'Awesome rule', + alertsCount: 1, + commentType: CommentType.alert, }; jest.mock('../../../common/lib/kibana'); @@ -54,7 +49,8 @@ describe('UserActionAvatar ', () => { it('does NOT render the link when the alert is undefined', async () => { const wrapper = mount( - + {/* @ts-expect-error */} + ); @@ -62,13 +58,13 @@ describe('UserActionAvatar ', () => { wrapper.find(`[data-test-subj="alert-rule-link-alert-id-1"]`).first().exists() ).toBeFalsy(); - expect(wrapper.text()).toBe('added an alert'); + expect(wrapper.text()).toBe('added an alert from '); }); it('does NOT render the link when the rule is undefined', async () => { const alert = { - _id: 'alert-id-1', - _index: 'alert-index-1', + alertId: 'alert-id-1', + commentType: CommentType.alert, }; const wrapper = mount( @@ -82,7 +78,7 @@ describe('UserActionAvatar ', () => { wrapper.find(`[data-test-subj="alert-rule-link-alert-id-1"]`).first().exists() ).toBeFalsy(); - expect(wrapper.text()).toBe('added an alert'); + expect(wrapper.text()).toBe('added an alert from '); }); it('navigate to app on link click', async () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx index 183116167f84a2..2a604b7c54d6b4 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx @@ -6,24 +6,36 @@ */ import React, { memo, useCallback } from 'react'; -import { EuiLink } from '@elastic/eui'; +import { EuiText, EuiLoadingSpinner } from '@elastic/eui'; import { APP_ID } from '../../../../common/constants'; import { useKibana } from '../../../common/lib/kibana'; -import { getRuleDetailsUrl } from '../../../common/components/link_to'; +import { getRuleDetailsUrl, useFormatUrl } from '../../../common/components/link_to'; import { SecurityPageName } from '../../../app/types'; -import { Alert } from '../case_view'; import * as i18n from './translations'; +import { CommentType } from '../../../../../case/common/api'; +import { LinkAnchor } from '../../../common/components/links'; interface Props { - alert: Alert | undefined; + alertId: string; + commentType: CommentType; + ruleId: string; + ruleName: string; + alertsCount?: number; + loadingAlertData?: boolean; } -const AlertCommentEventComponent: React.FC = ({ alert }) => { - const ruleName = alert?.rule?.name ?? null; - const ruleId = alert?.rule?.id ?? null; +const AlertCommentEventComponent: React.FC = ({ + alertId, + loadingAlertData = false, + ruleId, + ruleName, + alertsCount, + commentType, +}) => { const { navigateToApp } = useKibana().services.application; + const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.detections); const onLinkClick = useCallback( (ev: { preventDefault: () => void }) => { @@ -35,15 +47,37 @@ const AlertCommentEventComponent: React.FC = ({ alert }) => { [ruleId, navigateToApp] ); - return ruleId != null && ruleName != null ? ( + return commentType !== CommentType.generatedAlert ? ( <> {`${i18n.ALERT_COMMENT_LABEL_TITLE} `} - - {ruleName} - + {loadingAlertData && } + {!loadingAlertData && ruleId !== '' && ( + + {ruleName} + + )} + {!loadingAlertData && ruleId === '' && {ruleName}} ) : ( - <>{i18n.ALERT_RULE_DELETED_COMMENT_LABEL} + <> + {i18n.GENERATED_ALERT_COUNT_COMMENT_LABEL_TITLE(alertsCount ?? 0)}{' '} + {i18n.GENERATED_ALERT_COMMENT_LABEL_TITLE}{' '} + {loadingAlertData && } + {!loadingAlertData && ruleId !== '' && ( + + {ruleName} + + )} + {!loadingAlertData && ruleId === '' && {ruleName}} + ); }; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.tsx index 4bd4734e30671e..ff4e151197464c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.tsx @@ -18,24 +18,26 @@ interface UserActionCopyLinkProps { id: string; } -const UserActionCopyLinkComponent = ({ id }: UserActionCopyLinkProps) => { - const { detailName: caseId } = useParams<{ detailName: string }>(); +const UserActionCopyLinkComponent = ({ id: commentId }: UserActionCopyLinkProps) => { + const { detailName: caseId, subCaseId } = useParams<{ detailName: string; subCaseId?: string }>(); const { formatUrl } = useFormatUrl(SecurityPageName.case); const handleAnchorLink = useCallback(() => { copy( - formatUrl(getCaseDetailsUrlWithCommentId({ id: caseId, commentId: id }), { absolute: true }) + formatUrl(getCaseDetailsUrlWithCommentId({ id: caseId, commentId, subCaseId }), { + absolute: true, + }) ); - }, [caseId, formatUrl, id]); + }, [caseId, commentId, formatUrl, subCaseId]); return ( {i18n.COPY_REFERENCE_LINK}

}>
); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.test.tsx index 5d619a39d0e79a..789a6eb68e0fc7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.test.tsx @@ -8,20 +8,24 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; import { UserActionShowAlert } from './user_action_show_alert'; +import { RuleEcs } from '../../../../common/ecs/rule'; const props = { id: 'action-id', + alertId: 'alert-id', + index: 'alert-index', alert: { _id: 'alert-id', _index: 'alert-index', - '@timestamp': '2021-01-07T13:58:31.487Z', + timestamp: '2021-01-07T13:58:31.487Z', rule: { - id: 'rule-id', - name: 'Awesome Rule', - from: '2021-01-07T13:58:31.487Z', - to: '2021-01-07T14:58:31.487Z', - }, + id: ['rule-id'], + name: ['Awesome Rule'], + from: ['2021-01-07T13:58:31.487Z'], + to: ['2021-01-07T14:58:31.487Z'], + } as RuleEcs, }, + onShowAlertDetails: jest.fn(), }; describe('UserActionShowAlert ', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx index ea4994d1c80983..4f5ce00806417b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx @@ -7,25 +7,24 @@ import React, { memo, useCallback } from 'react'; import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; -import deepEqual from 'fast-deep-equal'; - -import { Alert } from '../case_view'; import * as i18n from './translations'; interface UserActionShowAlertProps { id: string; - alert: Alert; + alertId: string; + index: string; onShowAlertDetails: (alertId: string, index: string) => void; } const UserActionShowAlertComponent = ({ id, - alert, + alertId, + index, onShowAlertDetails, }: UserActionShowAlertProps) => { - const onClick = useCallback(() => onShowAlertDetails(alert._id, alert._index), [ - alert._id, - alert._index, + const onClick = useCallback(() => onShowAlertDetails(alertId, index), [ + alertId, + index, onShowAlertDetails, ]); return ( @@ -41,10 +40,4 @@ const UserActionShowAlertComponent = ({ ); }; -export const UserActionShowAlert = memo( - UserActionShowAlertComponent, - (prevProps, nextProps) => - prevProps.id === nextProps.id && - deepEqual(prevProps.alert, nextProps.alert) && - prevProps.onShowAlertDetails === nextProps.onShowAlertDetails -); +export const UserActionShowAlert = memo(UserActionShowAlertComponent); diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.ts b/x-pack/plugins/security_solution/public/cases/containers/api.ts index 00a45aadd2ae08..c87e210b42bc05 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/api.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { assign } from 'lodash'; + import { CasePatchRequest, CasePostRequest, @@ -16,6 +18,9 @@ import { CaseUserActionsResponse, CommentRequest, CommentType, + SubCasePatchRequest, + SubCaseResponse, + SubCasesResponse, User, } from '../../../../case/common/api'; @@ -25,6 +30,8 @@ import { CASE_STATUS_URL, CASE_TAGS_URL, CASES_URL, + SUB_CASE_DETAILS_URL, + SUB_CASES_PATCH_DEL_URL, } from '../../../../case/common/constants'; import { @@ -32,6 +39,8 @@ import { getCasePushUrl, getCaseDetailsUrl, getCaseUserActionUrl, + getSubCaseDetailsUrl, + getSubCaseUserActionUrl, } from '../../../../case/common/api/helpers'; import { KibanaServices } from '../../common/lib/kibana'; @@ -73,6 +82,34 @@ export const getCase = async ( return convertToCamelCase(decodeCaseResponse(response)); }; +export const getSubCase = async ( + caseId: string, + subCaseId: string, + includeComments: boolean = true, + signal: AbortSignal +): Promise => { + const [caseResponse, subCaseResponse] = await Promise.all([ + KibanaServices.get().http.fetch(getCaseDetailsUrl(caseId), { + method: 'GET', + query: { + includeComments: false, + }, + signal, + }), + KibanaServices.get().http.fetch(getSubCaseDetailsUrl(caseId, subCaseId), { + method: 'GET', + query: { + includeComments, + }, + signal, + }), + ]); + const response = assign(caseResponse, subCaseResponse); + const subCaseIndex = response.subCaseIds?.findIndex((scId) => scId === response.id) ?? -1; + response.title = `${response.title}${subCaseIndex >= 0 ? ` ${subCaseIndex + 1}` : ''}`; + return convertToCamelCase(decodeCaseResponse(response)); +}; + export const getCasesStatus = async (signal: AbortSignal): Promise => { const response = await KibanaServices.get().http.fetch(CASE_STATUS_URL, { method: 'GET', @@ -111,6 +148,21 @@ export const getCaseUserActions = async ( return convertArrayToCamelCase(decodeCaseUserActionsResponse(response)) as CaseUserActions[]; }; +export const getSubCaseUserActions = async ( + caseId: string, + subCaseId: string, + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch( + getSubCaseUserActionUrl(caseId, subCaseId), + { + method: 'GET', + signal, + } + ); + return convertArrayToCamelCase(decodeCaseUserActionsResponse(response)) as CaseUserActions[]; +}; + export const getCases = async ({ filterOptions = { search: '', @@ -167,6 +219,35 @@ export const patchCase = async ( return convertToCamelCase(decodeCasesResponse(response)); }; +export const patchSubCase = async ( + caseId: string, + subCaseId: string, + updatedSubCase: Pick, + version: string, + signal: AbortSignal +): Promise => { + const subCaseResponse = await KibanaServices.get().http.fetch( + SUB_CASE_DETAILS_URL, + { + method: 'PATCH', + body: JSON.stringify({ cases: [{ ...updatedSubCase, id: caseId, version }] }), + signal, + } + ); + const caseResponse = await KibanaServices.get().http.fetch( + getCaseDetailsUrl(caseId), + { + method: 'GET', + query: { + includeComments: false, + }, + signal, + } + ); + const response = subCaseResponse.map((subCaseResp) => assign(caseResponse, subCaseResp)); + return convertToCamelCase(decodeCasesResponse(response)); +}; + export const patchCasesStatus = async ( cases: BulkUpdateStatus[], signal: AbortSignal @@ -182,13 +263,15 @@ export const patchCasesStatus = async ( export const postComment = async ( newComment: CommentRequest, caseId: string, - signal: AbortSignal + signal: AbortSignal, + subCaseId?: string ): Promise => { const response = await KibanaServices.get().http.fetch( `${CASES_URL}/${caseId}/comments`, { method: 'POST', body: JSON.stringify(newComment), + ...(subCaseId ? { query: { subCaseId } } : {}), signal, } ); @@ -200,7 +283,8 @@ export const patchComment = async ( commentId: string, commentUpdate: string, version: string, - signal: AbortSignal + signal: AbortSignal, + subCaseId?: string ): Promise => { const response = await KibanaServices.get().http.fetch(getCaseCommentsUrl(caseId), { method: 'PATCH', @@ -210,6 +294,7 @@ export const patchComment = async ( id: commentId, version, }), + ...(subCaseId ? { query: { subCaseId } } : {}), signal, }); return convertToCamelCase(decodeCaseResponse(response)); @@ -224,6 +309,15 @@ export const deleteCases = async (caseIds: string[], signal: AbortSignal): Promi return response; }; +export const deleteSubCases = async (caseIds: string[], signal: AbortSignal): Promise => { + const response = await KibanaServices.get().http.fetch(SUB_CASES_PATCH_DEL_URL, { + method: 'DELETE', + query: { ids: JSON.stringify(caseIds) }, + signal, + }); + return response; +}; + export const pushCase = async ( caseId: string, connectorId: string, diff --git a/x-pack/plugins/security_solution/public/cases/containers/mock.ts b/x-pack/plugins/security_solution/public/cases/containers/mock.ts index 80d4816bedd53d..d8692da986cbef 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/mock.ts @@ -26,6 +26,7 @@ import { ConnectorTypes } from '../../../../case/common/api/connectors'; export { connectorsMock } from './configure/mock'; export const basicCaseId = 'basic-case-id'; +export const basicSubCaseId = 'basic-sub-case-id'; const basicCommentId = 'basic-comment-id'; const basicCreatedAt = '2020-02-19T23:06:33.798Z'; const basicUpdatedAt = '2020-02-20T15:02:57.995Z'; @@ -63,6 +64,10 @@ export const alertComment: Comment = { createdBy: elasticUser, pushedAt: null, pushedBy: null, + rule: { + id: 'rule-id-1', + name: 'Awesome rule', + }, updatedAt: null, updatedBy: null, version: 'WzQ3LDFc', @@ -95,6 +100,7 @@ export const basicCase: Case = { settings: { syncAlerts: true, }, + subCaseIds: [], }; export const basicCasePost: Case = { @@ -217,7 +223,7 @@ export const basicCaseSnake: CaseResponse = { external_service: null, updated_at: basicUpdatedAt, updated_by: elasticUserSnake, -}; +} as CaseResponse; export const casesStatusSnake: CasesStatusResponse = { count_closed_cases: 130, diff --git a/x-pack/plugins/security_solution/public/cases/containers/types.ts b/x-pack/plugins/security_solution/public/cases/containers/types.ts index 30ea8344434686..d2931a790bd798 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/types.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/types.ts @@ -18,7 +18,9 @@ import { AssociationType, } from '../../../../case/common/api'; -export { CaseConnector, ActionConnector } from '../../../../case/common/api'; +export { CaseConnector, ActionConnector, CaseStatuses } from '../../../../case/common/api'; + +export type AllCaseType = AssociationType & CaseType; export type Comment = CommentRequest & { associationType: AssociationType; @@ -52,26 +54,37 @@ export interface CaseExternalService { externalTitle: string; externalUrl: string; } -export interface Case { + +interface BasicCase { id: string; closedAt: string | null; closedBy: ElasticUser | null; comments: Comment[]; - connector: CaseConnector; createdAt: string; createdBy: ElasticUser; - description: string; - externalService: CaseExternalService | null; status: CaseStatuses; - tags: string[]; title: string; totalAlerts: number; totalComment: number; - type: CaseType; updatedAt: string | null; updatedBy: ElasticUser | null; version: string; +} + +export interface SubCase extends BasicCase { + associationType: AssociationType; + caseParentId: string; +} + +export interface Case extends BasicCase { + connector: CaseConnector; + description: string; + externalService: CaseExternalService | null; + subCases?: SubCase[] | null; + subCaseIds: string[]; settings: CaseAttributes['settings']; + tags: string[]; + type: CaseType; } export interface QueryParams { @@ -138,6 +151,7 @@ export interface ActionLicense { export interface DeleteCase { id: string; title?: string; + type?: CaseType; } export interface FieldMappings { @@ -153,7 +167,7 @@ export type UpdateKey = keyof Pick< export interface UpdateByKey { updateKey: UpdateKey; updateValue: CasePatchRequest[UpdateKey]; - fetchCaseUserActions?: (caseId: string) => void; + fetchCaseUserActions?: (caseId: string, subCaseId?: string) => void; updateCase?: (newCase: Case) => void; caseData: Case; onSuccess?: () => void; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx index b777b16b1c0c1c..923c20dcf8ebd5 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx @@ -12,7 +12,7 @@ import { useStateToaster, } from '../../common/components/toasters'; import * as i18n from './translations'; -import { deleteCases } from './api'; +import { deleteCases, deleteSubCases } from './api'; import { DeleteCase } from './types'; interface DeleteState { @@ -87,7 +87,13 @@ export const useDeleteCases = (): UseDeleteCase => { try { dispatch({ type: 'FETCH_INIT' }); const caseIds = cases.map((theCase) => theCase.id); - await deleteCases(caseIds, abortCtrl.signal); + // We don't allow user batch delete sub cases on UI at the moment. + if (cases[0].type != null || cases.length > 1) { + await deleteCases(caseIds, abortCtrl.signal); + } else { + await deleteSubCases(caseIds, abortCtrl.signal); + } + if (!cancel) { dispatch({ type: 'FETCH_SUCCESS', payload: true }); displaySuccessToast( diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx index 45827a4bebff80..1c4476e3cb2b7c 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx @@ -5,13 +5,14 @@ * 2.0. */ -import { useEffect, useReducer, useCallback } from 'react'; +import { isEmpty } from 'lodash'; +import { useEffect, useReducer, useCallback, useRef } from 'react'; import { CaseStatuses, CaseType } from '../../../../case/common/api'; import { Case } from './types'; import * as i18n from './translations'; import { errorToToaster, useStateToaster } from '../../common/components/toasters'; -import { getCase } from './api'; +import { getCase, getSubCase } from './api'; import { getNoneConnector } from '../components/configure_cases/utils'; interface CaseState { @@ -77,6 +78,7 @@ export const initialData: Case = { updatedAt: null, updatedBy: null, version: '', + subCaseIds: [], settings: { syncAlerts: true, }, @@ -87,31 +89,32 @@ export interface UseGetCase extends CaseState { updateCase: (newCase: Case) => void; } -export const useGetCase = (caseId: string): UseGetCase => { +export const useGetCase = (caseId: string, subCaseId?: string): UseGetCase => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: true, isError: false, data: initialData, }); const [, dispatchToaster] = useStateToaster(); + const abortCtrl = useRef(new AbortController()); + const didCancel = useRef(false); const updateCase = useCallback((newCase: Case) => { dispatch({ type: 'UPDATE_CASE', payload: newCase }); }, []); const callFetch = useCallback(async () => { - let didCancel = false; - const abortCtrl = new AbortController(); - const fetchData = async () => { dispatch({ type: 'FETCH_INIT' }); try { - const response = await getCase(caseId, true, abortCtrl.signal); - if (!didCancel) { + const response = await (subCaseId + ? getSubCase(caseId, subCaseId, true, abortCtrl.current.signal) + : getCase(caseId, true, abortCtrl.current.signal)); + if (!didCancel.current) { dispatch({ type: 'FETCH_SUCCESS', payload: response }); } } catch (error) { - if (!didCancel) { + if (!didCancel.current) { errorToToaster({ title: i18n.ERROR_TITLE, error: error.body && error.body.message ? new Error(error.body.message) : error, @@ -121,17 +124,22 @@ export const useGetCase = (caseId: string): UseGetCase => { } } }; + didCancel.current = false; + abortCtrl.current.abort(); + abortCtrl.current = new AbortController(); fetchData(); - return () => { - didCancel = true; - abortCtrl.abort(); - }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [caseId]); + }, [caseId, subCaseId]); useEffect(() => { - callFetch(); + if (!isEmpty(caseId)) { + callFetch(); + } + return () => { + didCancel.current = true; + abortCtrl.current.abort(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [caseId]); + }, [caseId, subCaseId]); return { ...state, fetchCase: callFetch, updateCase }; }; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx index 8ebd46e64296f6..12e5f6643351ff 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx @@ -6,12 +6,12 @@ */ import { isEmpty, uniqBy } from 'lodash/fp'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; import { errorToToaster, useStateToaster } from '../../common/components/toasters'; import { CaseFullExternalService } from '../../../../case/common/api/cases'; -import { getCaseUserActions } from './api'; +import { getCaseUserActions, getSubCaseUserActions } from './api'; import * as i18n from './translations'; import { CaseConnector, CaseExternalService, CaseUserActions, ElasticUser } from './types'; import { convertToCamelCase, parseString } from './utils'; @@ -46,7 +46,7 @@ export const initialData: CaseUserActionsState = { }; export interface UseGetCaseUserActions extends CaseUserActionsState { - fetchCaseUserActions: (caseId: string) => void; + fetchCaseUserActions: (caseId: string, subCaseId?: string) => void; } const getExternalService = (value: string): CaseExternalService | null => @@ -238,26 +238,29 @@ export const getPushedInfo = ( export const useGetCaseUserActions = ( caseId: string, - caseConnectorId: string + caseConnectorId: string, + subCaseId?: string ): UseGetCaseUserActions => { const [caseUserActionsState, setCaseUserActionsState] = useState( initialData ); - + const abortCtrl = useRef(new AbortController()); + const didCancel = useRef(false); const [, dispatchToaster] = useStateToaster(); const fetchCaseUserActions = useCallback( - (thisCaseId: string) => { - let didCancel = false; - const abortCtrl = new AbortController(); + (thisCaseId: string, thisSubCaseId?: string) => { const fetchData = async () => { - setCaseUserActionsState({ - ...caseUserActionsState, - isLoading: true, - }); try { - const response = await getCaseUserActions(thisCaseId, abortCtrl.signal); - if (!didCancel) { + setCaseUserActionsState({ + ...caseUserActionsState, + isLoading: true, + }); + + const response = await (thisSubCaseId + ? getSubCaseUserActions(thisCaseId, thisSubCaseId, abortCtrl.current.signal) + : getCaseUserActions(thisCaseId, abortCtrl.current.signal)); + if (!didCancel.current) { // Attention Future developer // We are removing the first item because it will always be the creation of the case // and we do not want it to simplify our life @@ -265,7 +268,11 @@ export const useGetCaseUserActions = ( ? uniqBy('actionBy.username', response).map((cau) => cau.actionBy) : []; - const caseUserActions = !isEmpty(response) ? response.slice(1) : []; + const caseUserActions = !isEmpty(response) + ? thisSubCaseId + ? response + : response.slice(1) + : []; setCaseUserActionsState({ caseUserActions, ...getPushedInfo(caseUserActions, caseConnectorId), @@ -275,7 +282,7 @@ export const useGetCaseUserActions = ( }); } } catch (error) { - if (!didCancel) { + if (!didCancel.current) { errorToToaster({ title: i18n.ERROR_TITLE, error: error.body && error.body.message ? new Error(error.body.message) : error, @@ -292,21 +299,24 @@ export const useGetCaseUserActions = ( } } }; + abortCtrl.current.abort(); + abortCtrl.current = new AbortController(); fetchData(); - return () => { - didCancel = true; - abortCtrl.abort(); - }; }, // eslint-disable-next-line react-hooks/exhaustive-deps - [caseUserActionsState, caseConnectorId] + [caseConnectorId] ); useEffect(() => { if (!isEmpty(caseId)) { - fetchCaseUserActions(caseId); + fetchCaseUserActions(caseId, subCaseId); } + + return () => { + didCancel.current = true; + abortCtrl.current.abort(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [caseId, caseConnectorId]); + }, [caseId, subCaseId]); return { ...caseUserActionsState, fetchCaseUserActions }; }; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx index f9d4454f63ffb6..42cd0deafa048f 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx @@ -9,7 +9,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { CommentType } from '../../../../case/common/api'; import { usePostComment, UsePostComment } from './use_post_comment'; -import { basicCaseId } from './mock'; +import { basicCaseId, basicSubCaseId } from './mock'; import * as api from './api'; jest.mock('./api'); @@ -40,7 +40,7 @@ describe('usePostComment', () => { }); }); - it('calls postComment with correct arguments', async () => { + it('calls postComment with correct arguments - case', async () => { const spyOnPostCase = jest.spyOn(api, 'postComment'); await act(async () => { @@ -49,9 +49,38 @@ describe('usePostComment', () => { ); await waitForNextUpdate(); - result.current.postComment(basicCaseId, samplePost, updateCaseCallback); + result.current.postComment({ + caseId: basicCaseId, + data: samplePost, + updateCase: updateCaseCallback, + }); + await waitForNextUpdate(); + expect(spyOnPostCase).toBeCalledWith(samplePost, basicCaseId, abortCtrl.signal, undefined); + }); + }); + + it('calls postComment with correct arguments - sub case', async () => { + const spyOnPostCase = jest.spyOn(api, 'postComment'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostComment() + ); + await waitForNextUpdate(); + + result.current.postComment({ + caseId: basicCaseId, + data: samplePost, + updateCase: updateCaseCallback, + subCaseId: basicSubCaseId, + }); await waitForNextUpdate(); - expect(spyOnPostCase).toBeCalledWith(samplePost, basicCaseId, abortCtrl.signal); + expect(spyOnPostCase).toBeCalledWith( + samplePost, + basicCaseId, + abortCtrl.signal, + basicSubCaseId + ); }); }); @@ -61,7 +90,11 @@ describe('usePostComment', () => { usePostComment() ); await waitForNextUpdate(); - result.current.postComment(basicCaseId, samplePost, updateCaseCallback); + result.current.postComment({ + caseId: basicCaseId, + data: samplePost, + updateCase: updateCaseCallback, + }); await waitForNextUpdate(); expect(result.current).toEqual({ isLoading: false, @@ -77,7 +110,11 @@ describe('usePostComment', () => { usePostComment() ); await waitForNextUpdate(); - result.current.postComment(basicCaseId, samplePost, updateCaseCallback); + result.current.postComment({ + caseId: basicCaseId, + data: samplePost, + updateCase: updateCaseCallback, + }); expect(result.current.isLoading).toBe(true); }); @@ -94,7 +131,11 @@ describe('usePostComment', () => { usePostComment() ); await waitForNextUpdate(); - result.current.postComment(basicCaseId, samplePost, updateCaseCallback); + result.current.postComment({ + caseId: basicCaseId, + data: samplePost, + updateCase: updateCaseCallback, + }); expect(result.current).toEqual({ isLoading: false, diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx index f2bd9d3f41f3ce..8fc8053c14f70d 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx @@ -42,8 +42,14 @@ const dataFetchReducer = (state: NewCommentState, action: Action): NewCommentSta } }; +interface PostComment { + caseId: string; + data: CommentRequest; + updateCase?: (newCase: Case) => void; + subCaseId?: string; +} export interface UsePostComment extends NewCommentState { - postComment: (caseId: string, data: CommentRequest, updateCase?: (newCase: Case) => void) => void; + postComment: (args: PostComment) => void; } export const usePostComment = (): UsePostComment => { @@ -54,13 +60,13 @@ export const usePostComment = (): UsePostComment => { const [, dispatchToaster] = useStateToaster(); const postMyComment = useCallback( - async (caseId: string, data: CommentRequest, updateCase?: (newCase: Case) => void) => { + async ({ caseId, data, updateCase, subCaseId }: PostComment) => { let cancel = false; const abortCtrl = new AbortController(); try { dispatch({ type: 'FETCH_INIT' }); - const response = await postComment(data, caseId, abortCtrl.signal); + const response = await postComment(data, caseId, abortCtrl.signal, subCaseId); if (!cancel) { dispatch({ type: 'FETCH_SUCCESS' }); if (updateCase) { diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx index 62560244fe9c80..0adf2cc0bf92ab 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx @@ -7,7 +7,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useUpdateCase, UseUpdateCase } from './use_update_case'; -import { basicCase } from './mock'; +import { basicCase, basicSubCaseId } from './mock'; import * as api from './api'; import { UpdateKey } from './types'; @@ -84,7 +84,27 @@ describe('useUpdateCase', () => { isError: false, updateCaseProperty: result.current.updateCaseProperty, }); - expect(fetchCaseUserActions).toBeCalledWith(basicCase.id); + expect(fetchCaseUserActions).toBeCalledWith(basicCase.id, undefined); + expect(updateCase).toBeCalledWith(basicCase); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + it('patch sub case', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateCase({ caseId: basicCase.id, subCaseId: basicSubCaseId }) + ); + await waitForNextUpdate(); + result.current.updateCaseProperty(sampleUpdate); + await waitForNextUpdate(); + expect(result.current).toEqual({ + updateKey: null, + isLoading: false, + isError: false, + updateCaseProperty: result.current.updateCaseProperty, + }); + expect(fetchCaseUserActions).toBeCalledWith(basicCase.id, basicSubCaseId); expect(updateCase).toBeCalledWith(basicCase); expect(onSuccess).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx index b2b919ae1422b0..23a23caeb71bdf 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx @@ -5,12 +5,12 @@ * 2.0. */ -import { useReducer, useCallback } from 'react'; +import { useReducer, useCallback, useEffect, useRef } from 'react'; import { errorToToaster, useStateToaster } from '../../common/components/toasters'; -import { patchCase } from './api'; -import { UpdateKey, UpdateByKey } from './types'; +import { patchCase, patchSubCase } from './api'; +import { UpdateKey, UpdateByKey, CaseStatuses } from './types'; import * as i18n from './translations'; import { createUpdateSuccessToaster } from './utils'; @@ -57,13 +57,21 @@ const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => export interface UseUpdateCase extends NewCaseState { updateCaseProperty: (updates: UpdateByKey) => void; } -export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase => { +export const useUpdateCase = ({ + caseId, + subCaseId, +}: { + caseId: string; + subCaseId?: string; +}): UseUpdateCase => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, isError: false, updateKey: null, }); const [, dispatchToaster] = useStateToaster(); + const abortCtrl = useRef(new AbortController()); + const didCancel = useRef(false); const dispatchUpdateCaseProperty = useCallback( async ({ @@ -75,20 +83,27 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase => onSuccess, onError, }: UpdateByKey) => { - let cancel = false; - const abortCtrl = new AbortController(); - try { + didCancel.current = false; + abortCtrl.current = new AbortController(); dispatch({ type: 'FETCH_INIT', payload: updateKey }); - const response = await patchCase( - caseId, - { [updateKey]: updateValue }, - caseData.version, - abortCtrl.signal - ); - if (!cancel) { + const response = await (updateKey === 'status' && subCaseId + ? patchSubCase( + caseId, + subCaseId, + { status: updateValue as CaseStatuses }, + caseData.version, + abortCtrl.current.signal + ) + : patchCase( + caseId, + { [updateKey]: updateValue }, + caseData.version, + abortCtrl.current.signal + )); + if (!didCancel.current) { if (fetchCaseUserActions != null) { - fetchCaseUserActions(caseId); + fetchCaseUserActions(caseId, subCaseId); } if (updateCase != null) { updateCase(response[0]); @@ -104,26 +119,31 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase => } } } catch (error) { - if (!cancel) { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + if (!didCancel.current) { + if (error.name !== 'AbortError') { + errorToToaster({ + title: i18n.ERROR_TITLE, + error: error.body && error.body.message ? new Error(error.body.message) : error, + dispatchToaster, + }); + } dispatch({ type: 'FETCH_FAILURE' }); if (onError) { onError(); } } } - return () => { - cancel = true; - abortCtrl.abort(); - }; }, // eslint-disable-next-line react-hooks/exhaustive-deps - [] + [caseId, subCaseId] ); + useEffect(() => { + return () => { + didCancel.current = true; + abortCtrl.current.abort(); + }; + }, []); + return { ...state, updateCaseProperty: dispatchUpdateCaseProperty }; }; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx index d7d98879459fe4..9ff266ad9c9881 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx @@ -7,7 +7,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useUpdateComment, UseUpdateComment } from './use_update_comment'; -import { basicCase, basicCaseCommentPatch } from './mock'; +import { basicCase, basicCaseCommentPatch, basicSubCaseId } from './mock'; import * as api from './api'; jest.mock('./api'); @@ -43,7 +43,7 @@ describe('useUpdateComment', () => { }); }); - it('calls patchComment with correct arguments', async () => { + it('calls patchComment with correct arguments - case', async () => { const spyOnPatchComment = jest.spyOn(api, 'patchComment'); await act(async () => { @@ -59,7 +59,30 @@ describe('useUpdateComment', () => { basicCase.comments[0].id, 'updated comment', basicCase.comments[0].version, - abortCtrl.signal + abortCtrl.signal, + undefined + ); + }); + }); + + it('calls patchComment with correct arguments - sub case', async () => { + const spyOnPatchComment = jest.spyOn(api, 'patchComment'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useUpdateComment() + ); + await waitForNextUpdate(); + + result.current.patchComment({ ...sampleUpdate, subCaseId: basicSubCaseId }); + await waitForNextUpdate(); + expect(spyOnPatchComment).toBeCalledWith( + basicCase.id, + basicCase.comments[0].id, + 'updated comment', + basicCase.comments[0].version, + abortCtrl.signal, + basicSubCaseId ); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx index 6222d993bb798b..e36b21823310eb 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx @@ -57,6 +57,7 @@ interface UpdateComment { commentId: string; commentUpdate: string; fetchUserActions: () => void; + subCaseId?: string; updateCase: (newCase: Case) => void; version: string; } @@ -78,6 +79,7 @@ export const useUpdateComment = (): UseUpdateComment => { commentId, commentUpdate, fetchUserActions, + subCaseId, updateCase, version, }: UpdateComment) => { @@ -90,7 +92,8 @@ export const useUpdateComment = (): UseUpdateComment => { commentId, commentUpdate, version, - abortCtrl.signal + abortCtrl.signal, + subCaseId ); if (!cancel) { updateCase(response); diff --git a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx index 701ecdf8580f00..edb84db89b878a 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx @@ -21,7 +21,10 @@ import { savedObjectReadOnlyErrorMessage, CaseCallOut } from '../components/call export const CaseDetailsPage = React.memo(() => { const history = useHistory(); const userPermissions = useGetUserSavedObjectPermissions(); - const { detailName: caseId } = useParams<{ detailName?: string }>(); + const { detailName: caseId, subCaseId } = useParams<{ + detailName?: string; + subCaseId?: string; + }>(); const search = useGetUrlSearch(navTabs.case); if (userPermissions != null && !userPermissions.read) { @@ -38,7 +41,11 @@ export const CaseDetailsPage = React.memo(() => { messages={[{ ...savedObjectReadOnlyErrorMessage }]} /> )} - + diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx index 32c94e593665f8..314bdc9bfd117f 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx @@ -15,7 +15,9 @@ import { ConfigureCasesPage } from './configure_cases'; const casesPagePath = ''; const caseDetailsPagePath = `${casesPagePath}/:detailName`; -const caseDetailsPagePathWithCommentId = `${casesPagePath}/:detailName/:commentId`; +const subCaseDetailsPagePath = `${caseDetailsPagePath}/sub-cases/:subCaseId`; +const caseDetailsPagePathWithCommentId = `${caseDetailsPagePath}/:commentId`; +const subCaseDetailsPagePathWithCommentId = `${subCaseDetailsPagePath}/:commentId`; const createCasePagePath = `${casesPagePath}/create`; const configureCasesPagePath = `${casesPagePath}/configure`; @@ -27,7 +29,13 @@ const CaseContainerComponent: React.FC = () => ( - + + + + + + + diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx index 7a7da6f89306cf..82d8aac904e9b3 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx +++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx @@ -9,19 +9,43 @@ import { appendSearch } from './helpers'; export const getCaseUrl = (search?: string | null) => `${appendSearch(search ?? undefined)}`; -export const getCaseDetailsUrl = ({ id, search }: { id: string; search?: string | null }) => - `/${encodeURIComponent(id)}${appendSearch(search ?? undefined)}`; +export const getCaseDetailsUrl = ({ + id, + search, + subCaseId, +}: { + id: string; + search?: string | null; + subCaseId?: string; +}) => { + if (subCaseId) { + return `/${encodeURIComponent(id)}/sub-cases/${encodeURIComponent(subCaseId)}${appendSearch( + search ?? undefined + )}`; + } + return `/${encodeURIComponent(id)}${appendSearch(search ?? undefined)}`; +}; export const getCaseDetailsUrlWithCommentId = ({ id, commentId, search, + subCaseId, }: { id: string; commentId: string; search?: string | null; -}) => - `/${encodeURIComponent(id)}/${encodeURIComponent(commentId)}${appendSearch(search ?? undefined)}`; + subCaseId?: string; +}) => { + if (subCaseId) { + return `/${encodeURIComponent(id)}/sub-cases/${encodeURIComponent( + subCaseId + )}/${encodeURIComponent(commentId)}${appendSearch(search ?? undefined)}`; + } + return `/${encodeURIComponent(id)}/${encodeURIComponent(commentId)}${appendSearch( + search ?? undefined + )}`; +}; export const getCreateCaseUrl = (search?: string | null) => `/create${appendSearch(search ?? undefined)}`; diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx index 6b4148db2b1eeb..8e2f57a1a597c8 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx @@ -164,24 +164,25 @@ export const NetworkDetailsLink = React.memo(NetworkDetailsLinkComponent); const CaseDetailsLinkComponent: React.FC<{ children?: React.ReactNode; detailName: string; + subCaseId?: string; title?: string; -}> = ({ children, detailName, title }) => { +}> = ({ children, detailName, subCaseId, title }) => { const { formatUrl, search } = useFormatUrl(SecurityPageName.case); const { navigateToApp } = useKibana().services.application; const goToCaseDetails = useCallback( (ev) => { ev.preventDefault(); navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { - path: getCaseDetailsUrl({ id: detailName, search }), + path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), }); }, - [detailName, navigateToApp, search] + [detailName, navigateToApp, search, subCaseId] ); return ( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index 143c39daace66a..7d577659d66e20 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -421,7 +421,7 @@ describe('alert actions', () => { ...mockEcsDataWithAlert, timestamp: '2020-03-20T17:59:46.349Z', }; - const result = determineToAndFrom({ ecsData: ecsDataMock }); + const result = determineToAndFrom({ ecs: ecsDataMock }); expect(result.from).toEqual('2020-03-20T17:54:46.349Z'); expect(result.to).toEqual('2020-03-20T17:59:46.349Z'); @@ -431,7 +431,7 @@ describe('alert actions', () => { const { timestamp, ...ecsDataMock } = { ...mockEcsDataWithAlert, }; - const result = determineToAndFrom({ ecsData: ecsDataMock }); + const result = determineToAndFrom({ ecs: ecsDataMock }); expect(result.from).toEqual('2020-03-01T17:54:46.349Z'); expect(result.to).toEqual('2020-03-01T17:59:46.349Z'); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 9d38e2b369fa16..14ccae250ac487 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -39,6 +39,7 @@ import { } from './helpers'; import { KueryFilterQueryKind } from '../../../common/store'; import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; +import { esFilters } from '../../../../../../../src/plugins/data/public'; export const getUpdateAlertsQuery = (eventIds: Readonly) => { return { @@ -102,17 +103,32 @@ export const updateAlertStatusAction = async ({ } }; -export const determineToAndFrom = ({ ecsData }: { ecsData: Ecs }) => { +export const determineToAndFrom = ({ ecs }: { ecs: Ecs[] | Ecs }) => { + if (Array.isArray(ecs)) { + const timestamps = ecs.reduce((acc, item) => { + if (item.timestamp != null) { + const dateTimestamp = new Date(item.timestamp); + if (!acc.includes(dateTimestamp.valueOf())) { + return [...acc, dateTimestamp.valueOf()]; + } + } + return acc; + }, []); + return { + from: new Date(Math.min(...timestamps)).toISOString(), + to: new Date(Math.max(...timestamps)).toISOString(), + }; + } + const ecsData = ecs as Ecs; const ellapsedTimeRule = moment.duration( moment().diff( - dateMath.parse(ecsData.signal?.rule?.from != null ? ecsData.signal?.rule?.from[0] : 'now-0s') + dateMath.parse(ecsData?.signal?.rule?.from != null ? ecsData.signal?.rule?.from[0] : 'now-0s') ) ); - - const from = moment(ecsData.timestamp ?? new Date()) + const from = moment(ecsData?.timestamp ?? new Date()) .subtract(ellapsedTimeRule) .toISOString(); - const to = moment(ecsData.timestamp ?? new Date()).toISOString(); + const to = moment(ecsData?.timestamp ?? new Date()).toISOString(); return { to, from }; }; @@ -128,37 +144,41 @@ const getFiltersFromRule = (filters: string[]): Filter[] => }, [] as Filter[]); export const getThresholdAggregationDataProvider = ( - ecsData: Ecs, + ecsData: Ecs | Ecs[], nonEcsData: TimelineNonEcsData[] ): DataProvider[] => { - const aggregationField = ecsData.signal?.rule?.threshold?.field!; - const aggregationValue = - get(aggregationField, ecsData) ?? find(['field', aggregationField], nonEcsData)?.value; - const dataProviderValue = Array.isArray(aggregationValue) - ? aggregationValue[0] - : aggregationValue; + const thresholdEcsData: Ecs[] = Array.isArray(ecsData) ? ecsData : [ecsData]; + return thresholdEcsData.reduce((acc, tresholdData) => { + const aggregationField = tresholdData.signal?.rule?.threshold?.field!; + const aggregationValue = + get(aggregationField, tresholdData) ?? find(['field', aggregationField], nonEcsData)?.value; + const dataProviderValue = Array.isArray(aggregationValue) + ? aggregationValue[0] + : aggregationValue; - if (!dataProviderValue) { - return []; - } + if (!dataProviderValue) { + return acc; + } - const aggregationFieldId = aggregationField.replace('.', '-'); + const aggregationFieldId = aggregationField.replace('.', '-'); - return [ - { - and: [], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-${aggregationFieldId}-${dataProviderValue}`, - name: aggregationField, - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: aggregationField, - value: dataProviderValue, - operator: ':', + return [ + ...acc, + { + and: [], + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-${aggregationFieldId}-${dataProviderValue}`, + name: aggregationField, + enabled: true, + excluded: false, + kqlQuery: '', + queryMatch: { + field: aggregationField, + value: dataProviderValue, + operator: ':', + }, }, - }, - ]; + ]; + }, []); }; export const isEqlRuleWithGroupId = (ecsData: Ecs) => @@ -169,20 +189,134 @@ export const isEqlRuleWithGroupId = (ecsData: Ecs) => export const isThresholdRule = (ecsData: Ecs) => ecsData.signal?.rule?.type?.length && ecsData.signal?.rule?.type[0] === 'threshold'; +export const buildAlertsKqlFilter = ( + key: '_id' | 'signal.group.id', + alertIds: string[] +): Filter[] => { + return [ + { + query: { + bool: { + filter: { + ids: { + values: alertIds, + }, + }, + }, + }, + meta: { + alias: 'Alert Ids', + negate: false, + disabled: false, + type: 'phrases', + key, + value: alertIds.join(), + params: alertIds, + }, + $state: { + store: esFilters.FilterStateStore.APP_STATE, + }, + }, + ]; +}; + +export const buildTimelineDataProviderOrFilter = ( + alertsIds: string[], + _id: string +): { filters: Filter[]; dataProviders: DataProvider[] } => { + if (!isEmpty(alertsIds)) { + return { + dataProviders: [], + filters: buildAlertsKqlFilter('_id', alertsIds), + }; + } + return { + filters: [], + dataProviders: [ + { + and: [], + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${_id}`, + name: _id, + enabled: true, + excluded: false, + kqlQuery: '', + queryMatch: { + field: '_id', + value: _id, + operator: ':' as const, + }, + }, + ], + }; +}; + +export const buildEqlDataProviderOrFilter = ( + alertsIds: string[], + ecs: Ecs[] | Ecs +): { filters: Filter[]; dataProviders: DataProvider[] } => { + if (!isEmpty(alertsIds) && Array.isArray(ecs)) { + return { + dataProviders: [], + filters: buildAlertsKqlFilter( + 'signal.group.id', + ecs.reduce((acc, ecsData) => { + const signalGroupId = ecsData.signal?.group?.id?.length + ? ecsData.signal?.group?.id[0] + : 'unknown-signal-group-id'; + if (!acc.includes(signalGroupId)) { + return [...acc, signalGroupId]; + } + return acc; + }, []) + ), + }; + } else if (!Array.isArray(ecs)) { + const signalGroupId = ecs.signal?.group?.id?.length + ? ecs.signal?.group?.id[0] + : 'unknown-signal-group-id'; + return { + dataProviders: [ + { + and: [], + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${signalGroupId}`, + name: ecs._id, + enabled: true, + excluded: false, + kqlQuery: '', + queryMatch: { + field: 'signal.group.id', + value: signalGroupId, + operator: ':' as const, + }, + }, + ], + filters: [], + }; + } + return { filters: [], dataProviders: [] }; +}; + export const sendAlertToTimelineAction = async ({ apolloClient, createTimeline, - ecsData, + ecsData: ecs, nonEcsData, updateTimelineIsLoading, searchStrategyClient, }: SendAlertToTimelineActionProps) => { + /* FUTURE DEVELOPER + * We are making an assumption here that if you have an array of ecs data they are all coming from the same rule + * but we still want to determine the filter for each alerts + */ + const ecsData: Ecs = Array.isArray(ecs) && ecs.length > 0 ? ecs[0] : (ecs as Ecs); + const alertIds = Array.isArray(ecs) ? ecs.map((d) => d._id) : []; const noteContent = ecsData.signal?.rule?.note != null ? ecsData.signal?.rule?.note[0] : ''; const timelineId = ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : ''; - const { to, from } = determineToAndFrom({ ecsData }); + const { to, from } = determineToAndFrom({ ecs }); - if (!isEmpty(timelineId) && apolloClient != null) { + // For now we do not want to populate the template timeline if we have alertIds + if (!isEmpty(timelineId) && apolloClient != null && isEmpty(alertIds)) { try { updateTimelineIsLoading({ id: TimelineId.active, isLoading: true }); const [responseTimeline, eventDataResp] = await Promise.all([ @@ -275,7 +409,7 @@ export const sendAlertToTimelineAction = async ({ ...timelineDefaults, description: `_id: ${ecsData._id}`, filters: getFiltersFromRule(ecsData.signal?.rule?.filters as string[]), - dataProviders: [...getThresholdAggregationDataProvider(ecsData, nonEcsData)], + dataProviders: [...getThresholdAggregationDataProvider(ecs, nonEcsData)], id: TimelineId.active, indexNames: [], dateRange: { @@ -301,36 +435,11 @@ export const sendAlertToTimelineAction = async ({ ruleNote: noteContent, }); } else { - let dataProviders = [ - { - and: [], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${ecsData._id}`, - name: ecsData._id, - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: '_id', - value: ecsData._id, - operator: ':' as const, - }, - }, - ]; + let { dataProviders, filters } = buildTimelineDataProviderOrFilter(alertIds ?? [], ecsData._id); if (isEqlRuleWithGroupId(ecsData)) { - const signalGroupId = ecsData.signal?.group?.id?.length - ? ecsData.signal?.group?.id[0] - : 'unknown-signal-group-id'; - dataProviders = [ - { - ...dataProviders[0], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${signalGroupId}`, - queryMatch: { - field: 'signal.group.id', - value: signalGroupId, - operator: ':' as const, - }, - }, - ]; + const tempEql = buildEqlDataProviderOrFilter(alertIds ?? [], ecs); + dataProviders = tempEql.dataProviders; + filters = tempEql.filters; } return createTimeline({ @@ -346,6 +455,7 @@ export const sendAlertToTimelineAction = async ({ end: to, }, eventType: 'all', + filters, kqlQuery: { filterQuery: { kuery: { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx index d6813fdef8e54e..2f0fee980c2181 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx @@ -24,14 +24,18 @@ import { } from '../translations'; interface InvestigateInTimelineActionProps { - ariaLabel?: string; - ecsRowData: Ecs; + ecsRowData: Ecs | Ecs[] | null; nonEcsRowData: TimelineNonEcsData[]; + ariaLabel?: string; + alertIds?: string[]; + fetchEcsAlertsData?: (alertIds?: string[]) => Promise; } const InvestigateInTimelineActionComponent: React.FC = ({ ariaLabel = ACTION_INVESTIGATE_IN_TIMELINE_ARIA_LABEL, + alertIds, ecsRowData, + fetchEcsAlertsData, nonEcsRowData, }) => { const { @@ -66,25 +70,42 @@ const InvestigateInTimelineActionComponent: React.FC - sendAlertToTimelineAction({ - apolloClient, - createTimeline, - ecsData: ecsRowData, - nonEcsData: nonEcsRowData, - searchStrategyClient, - updateTimelineIsLoading, - }), - [ - apolloClient, - createTimeline, - ecsRowData, - nonEcsRowData, - searchStrategyClient, - updateTimelineIsLoading, - ] - ); + const investigateInTimelineAlertClick = useCallback(async () => { + try { + if (ecsRowData != null) { + await sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData: ecsRowData, + nonEcsData: nonEcsRowData, + searchStrategyClient, + updateTimelineIsLoading, + }); + } + if (ecsRowData == null && fetchEcsAlertsData) { + const alertsEcsData = await fetchEcsAlertsData(alertIds); + await sendAlertToTimelineAction({ + apolloClient, + createTimeline, + ecsData: alertsEcsData, + nonEcsData: nonEcsRowData, + searchStrategyClient, + updateTimelineIsLoading, + }); + } + } catch { + // TODO show a toaster that something went wrong + } + }, [ + alertIds, + apolloClient, + createTimeline, + ecsRowData, + fetchEcsAlertsData, + nonEcsRowData, + searchStrategyClient, + updateTimelineIsLoading, + ]); return ( ; createTimeline: CreateTimeline; - ecsData: Ecs; + ecsData: Ecs | Ecs[]; nonEcsData: TimelineNonEcsData[]; updateTimelineIsLoading: UpdateTimelineLoading; searchStrategyClient: ISearchStart; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_query.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_query.tsx index 475160b7872723..8557e1082c1cb9 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_query.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_query.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { isEmpty } from 'lodash'; import React, { SetStateAction, useEffect, useState } from 'react'; import { fetchQueryAlerts } from './api'; @@ -80,7 +81,9 @@ export const useQueryAlerts = ( } }; - fetchData(); + if (!isEmpty(query)) { + fetchData(); + } return () => { isSubscribed = false; abortCtrl.abort(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts index e33ee4d5762ab8..15261ab5fad01b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts @@ -172,8 +172,10 @@ export const singleBulkCreate = async ({ logger.debug(buildRuleMessage(`took property says bulk took: ${response.took} milliseconds`)); const createdItems = filteredEvents.hits.hits - .map((doc) => - buildBulkBody({ + .map((doc, index) => ({ + _id: response.items[index].create?._id ?? '', + _index: response.items[index].create?._index ?? '', + ...buildBulkBody({ doc, ruleParams, id, @@ -187,8 +189,8 @@ export const singleBulkCreate = async ({ enabled, tags, throttle, - }) - ) + }), + })) .filter((_, index) => get(response.items[index], 'create.status') === 201); const createdItemsCount = createdItems.length; const duplicateSignalsCount = countBy(response.items, 'create.status')['409']; @@ -263,7 +265,11 @@ export const bulkInsertSignals = async ( const createdItemsCount = countBy(response.items, 'create.status')['201'] ?? 0; const createdItems = signals - .map((doc) => doc._source) + .map((doc, index) => ({ + ...doc._source, + _id: response.items[index].create?._id ?? '', + _index: response.items[index].create?._index ?? '', + })) .filter((_, index) => get(response.items[index], 'create.status') === 201); logger.debug(`bulk created ${createdItemsCount} signals`); return { bulkCreateDuration: makeFloatString(end - start), createdItems, createdItemsCount }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 344a07e53e3edb..a5d4b5d991b4a8 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17250,7 +17250,6 @@ "xpack.securitySolution.case.caseView.reporterLabel": "報告者", "xpack.securitySolution.case.caseView.requiredUpdateToExternalService": "{ externalService }インシデントの更新が必要です", "xpack.securitySolution.case.caseView.sendEmalLinkAria": "クリックすると、{user}に電子メールを送信します", - "xpack.securitySolution.case.caseView.showAlertDeletedTooltip": "アラートが見つかりません", "xpack.securitySolution.case.caseView.showAlertTooltip": "アラートの詳細を表示", "xpack.securitySolution.case.caseView.statusLabel": "ステータス", "xpack.securitySolution.case.caseView.tags": "タグ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 579a06d44e6595..f84b993fe56336 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17293,7 +17293,6 @@ "xpack.securitySolution.case.caseView.reporterLabel": "报告者", "xpack.securitySolution.case.caseView.requiredUpdateToExternalService": "需要更新 { externalService } 事件", "xpack.securitySolution.case.caseView.sendEmalLinkAria": "单击可向 {user} 发送电子邮件", - "xpack.securitySolution.case.caseView.showAlertDeletedTooltip": "未找到告警", "xpack.securitySolution.case.caseView.showAlertTooltip": "显示告警详情", "xpack.securitySolution.case.caseView.statusLabel": "状态", "xpack.securitySolution.case.caseView.tags": "标签", diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts index 2250b481c37291..86b1c3031cbef7 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/patch_comment.ts @@ -95,6 +95,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: 'id', + name: 'name', + }, }) .expect(400); }); @@ -110,6 +114,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.generatedAlert, alerts: [{ _id: 'id1' }], index: 'test-index', + rule: { + id: 'id', + name: 'name', + }, }) .expect(400); }); @@ -167,6 +175,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, alertId: 'new-id', index: postCommentAlertReq.index, + rule: { + id: 'id', + name: 'name', + }, }) .expect(200); @@ -230,6 +242,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, alertId: 'test-id', index: 'test-index', + rule: { + id: 'id', + name: 'name', + }, }) .expect(400); }); @@ -302,6 +318,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, index: 'test-index', alertId: 'test-id', + rule: { + id: 'id', + name: 'name', + }, }; for (const attribute of ['alertId', 'index']) { @@ -341,6 +361,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, index: 'test-index', alertId: 'test-id', + rule: { + id: 'id', + name: 'name', + }, [attribute]: attribute, }) .expect(400); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts b/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts index 1ce011985d9e63..fb095c117cdfb0 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/comments/post_comment.ts @@ -148,6 +148,10 @@ export default ({ getService }: FtrProviderContext): void => { type: CommentType.alert, index: 'test-index', alertId: 'test-id', + rule: { + id: 'id', + name: 'name', + }, }; for (const attribute of ['alertId', 'index']) { @@ -176,6 +180,10 @@ export default ({ getService }: FtrProviderContext): void => { [attribute]: attribute, alertId: 'test-id', index: 'test-index', + rule: { + id: 'id', + name: 'name', + }, }) .expect(400); } @@ -296,6 +304,10 @@ export default ({ getService }: FtrProviderContext): void => { .send({ alertId: alert._id, index: alert._index, + rule: { + id: 'id', + name: 'name', + }, type: CommentType.alert, }) .expect(200); @@ -346,6 +358,10 @@ export default ({ getService }: FtrProviderContext): void => { .send({ alertId: alert._id, index: alert._index, + rule: { + id: 'id', + name: 'name', + }, type: CommentType.alert, }) .expect(200); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts index dcc49152e4db8c..43d6be196da0d6 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/patch_cases.ts @@ -402,6 +402,10 @@ export default ({ getService }: FtrProviderContext): void => { .send({ alertId: alert._id, index: alert._index, + rule: { + id: 'id', + name: 'name', + }, type: CommentType.alert, }) .expect(200); @@ -453,6 +457,10 @@ export default ({ getService }: FtrProviderContext): void => { alertId: alert._id, index: alert._index, type: CommentType.alert, + rule: { + id: 'id', + name: 'name', + }, }) .expect(200); @@ -503,6 +511,10 @@ export default ({ getService }: FtrProviderContext): void => { .send({ alertId: alert._id, index: alert._index, + rule: { + id: 'id', + name: 'name', + }, type: CommentType.alert, }) .expect(200); @@ -570,6 +582,10 @@ export default ({ getService }: FtrProviderContext): void => { alertId: alert._id, index: alert._index, type: CommentType.alert, + rule: { + id: 'id', + name: 'name', + }, }) .expect(200); diff --git a/x-pack/test/case_api_integration/basic/tests/connectors/case.ts b/x-pack/test/case_api_integration/basic/tests/connectors/case.ts index 01dd6ed5404c2e..4812ead2c4c787 100644 --- a/x-pack/test/case_api_integration/basic/tests/connectors/case.ts +++ b/x-pack/test/case_api_integration/basic/tests/connectors/case.ts @@ -736,7 +736,12 @@ export default ({ getService }: FtrProviderContext): void => { subAction: 'addComment', subActionParams: { caseId: caseRes.body.id, - comment: { alertId: alert._id, index: alert._index, type: CommentType.alert }, + comment: { + alertId: alert._id, + index: alert._index, + type: CommentType.alert, + rule: { id: 'id', name: 'name' }, + }, }, }; @@ -784,7 +789,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); createdActionId = createdAction.id; - const comment = { alertId: 'test-id', index: 'test-index', type: CommentType.alert }; + const comment = { + alertId: 'test-id', + index: 'test-index', + type: CommentType.alert, + rule: { id: 'id', name: 'name' }, + }; const params = { subAction: 'addComment', subActionParams: { @@ -876,7 +886,12 @@ export default ({ getService }: FtrProviderContext): void => { subAction: 'addComment', subActionParams: { caseId: '123', - comment: { alertId: 'test-id', index: 'test-index', type: CommentType.alert }, + comment: { + alertId: 'test-id', + index: 'test-index', + type: CommentType.alert, + rule: { id: 'id', name: 'name' }, + }, }, }; @@ -1017,7 +1032,12 @@ export default ({ getService }: FtrProviderContext): void => { subAction: 'addComment', subActionParams: { caseId: caseRes.body.id, - comment: { alertId: 'test-id', index: 'test-index', type: CommentType.alert }, + comment: { + alertId: 'test-id', + index: 'test-index', + type: CommentType.alert, + rule: { id: 'id', name: 'name' }, + }, }, }; diff --git a/x-pack/test/case_api_integration/common/lib/mock.ts b/x-pack/test/case_api_integration/common/lib/mock.ts index 2f4fa1b30f5649..f6fd2b1a6b3bed 100644 --- a/x-pack/test/case_api_integration/common/lib/mock.ts +++ b/x-pack/test/case_api_integration/common/lib/mock.ts @@ -8,6 +8,7 @@ import { CommentSchemaType, ContextTypeGeneratedAlertType, + createAlertsString, isCommentGeneratedAlert, transformConnectorComment, } from '../../../../plugins/case/server/connectors'; @@ -70,12 +71,15 @@ export const postCommentUserReq: CommentRequestUserType = { export const postCommentAlertReq: CommentRequestAlertType = { alertId: 'test-id', index: 'test-index', + rule: { id: 'test-rule-id', name: 'test-index-id' }, type: CommentType.alert, }; export const postCommentGenAlertReq: ContextTypeGeneratedAlertType = { - alerts: [{ _id: 'test-id' }, { _id: 'test-id2' }], - index: 'test-index', + alerts: createAlertsString([ + { _id: 'test-id', _index: 'test-index', ruleId: 'rule-id', ruleName: 'rule name' }, + { _id: 'test-id2', _index: 'test-index', ruleId: 'rule-id', ruleName: 'rule name' }, + ]), type: CommentType.generatedAlert, }; From 2b0481cbbf615891f9a0d1bed749ee44d26941b8 Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Wed, 17 Feb 2021 20:59:16 -0500 Subject: [PATCH 23/93] [Security Solution] Add searchDeepLinks to security solution (#89772) * WIP add search for siem entities * Remove frontend search provider, use searchDeepLinks from core * Comment and use both functions used to generate subPlugin meta information * Use correct url for timeline templates * Remove case management attibute from saved object type * Remove unused globalSearch plugin from kibana.json * Add comments, use Subject instead of BehaviorSubject for appUpdaters * Unsubscribe from license on plugin stop --- .../public/app/search/index.test.ts | 23 ++ .../public/app/search/index.ts | 234 ++++++++++++++++++ .../security_solution/public/app/types.ts | 12 +- .../security_solution/public/plugin.tsx | 33 ++- .../plugins/security_solution/public/types.ts | 3 +- 5 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/app/search/index.test.ts create mode 100644 x-pack/plugins/security_solution/public/app/search/index.ts diff --git a/x-pack/plugins/security_solution/public/app/search/index.test.ts b/x-pack/plugins/security_solution/public/app/search/index.test.ts new file mode 100644 index 00000000000000..d6c36e89558d02 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/search/index.test.ts @@ -0,0 +1,23 @@ +/* + * 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 { getSearchDeepLinksAndKeywords } from '.'; +import { SecurityPageName } from '../../../common/constants'; + +describe('public search functions', () => { + it('returns a subset of links for basic license, full set for platinum', () => { + const basicLicense = 'basic'; + const platinumLicense = 'platinum'; + for (const pageName of Object.values(SecurityPageName)) { + expect.assertions(Object.values(SecurityPageName).length * 2); + const basicLinkCount = + getSearchDeepLinksAndKeywords(pageName, basicLicense).searchDeepLinks?.length || 0; + const platinumLinks = getSearchDeepLinksAndKeywords(pageName, platinumLicense); + expect(platinumLinks.searchDeepLinks?.length).toBeGreaterThanOrEqual(basicLinkCount); + expect(platinumLinks.keywords?.length).not.toBe(null); + } + }); +}); diff --git a/x-pack/plugins/security_solution/public/app/search/index.ts b/x-pack/plugins/security_solution/public/app/search/index.ts new file mode 100644 index 00000000000000..bddb43588bb6d1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/search/index.ts @@ -0,0 +1,234 @@ +/* + * 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 { Subject } from 'rxjs'; + +import { AppUpdater } from 'src/core/public'; +import { LicenseType } from '../../../../licensing/common/types'; +import { SecuritySubPluginNames, SecurityDeepLinks } from '../types'; +import { AppMeta } from '../../../../../../src/core/public'; + +const securityDeepLinks: SecurityDeepLinks = { + detections: { + base: [ + { + id: 'siemDetectionRules', + title: i18n.translate('xpack.securitySolution.search.detections.manage', { + defaultMessage: 'Manage Rules', + }), + keywords: ['rules'], + path: '/rules', + }, + ], + }, + hosts: { + base: [ + { + id: 'authentications', + title: i18n.translate('xpack.securitySolution.search.hosts.authentications', { + defaultMessage: 'Authentications', + }), + path: '/authentications', + }, + { + id: 'uncommonProcesses', + title: i18n.translate('xpack.securitySolution.search.hosts.uncommonProcesses', { + defaultMessage: 'Uncommon Processes', + }), + path: '/uncommonProcesses', + }, + { + id: 'events', + title: i18n.translate('xpack.securitySolution.search.hosts.events', { + defaultMessage: 'Events', + }), + path: '/events', + }, + { + id: 'externalAlerts', + title: i18n.translate('xpack.securitySolution.search.hosts.externalAlerts', { + defaultMessage: 'External Alerts', + }), + path: '/alerts', + }, + ], + premium: [ + { + id: 'anomalies', + title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', { + defaultMessage: 'Anomalies', + }), + path: '/anomalies', + }, + ], + }, + network: { + base: [ + { + id: 'dns', + title: i18n.translate('xpack.securitySolution.search.network.dns', { + defaultMessage: 'DNS', + }), + path: '/dns', + }, + { + id: 'http', + title: i18n.translate('xpack.securitySolution.search.network.http', { + defaultMessage: 'HTTP', + }), + path: '/http', + }, + { + id: 'tls', + title: i18n.translate('xpack.securitySolution.search.network.tls', { + defaultMessage: 'TLS', + }), + path: '/tls', + }, + { + id: 'externalAlertsNetwork', + title: i18n.translate('xpack.securitySolution.search.network.externalAlerts', { + defaultMessage: 'External Alerts', + }), + path: '/external-alerts', + }, + ], + premium: [ + { + id: 'anomalies', + title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', { + defaultMessage: 'Anomalies', + }), + path: '/anomalies', + }, + ], + }, + timelines: { + base: [ + { + id: 'timelineTemplates', + title: i18n.translate('xpack.securitySolution.search.timeline.templates', { + defaultMessage: 'Templates', + }), + path: '/template', + }, + ], + }, + overview: { + base: [], + }, + case: { + base: [], + premium: [ + { + id: 'configure', + title: i18n.translate('xpack.securitySolution.search.cases.configure', { + defaultMessage: 'Configure Cases', + }), + path: '/configure', + }, + ], + }, + administration: { + base: [ + { + id: 'trustApplications', + title: i18n.translate('xpack.securitySolution.search.administration.trustedApps', { + defaultMessage: 'Trusted Applications', + }), + path: '/trusted_apps', + }, + ], + }, +}; + +const subpluginKeywords: { [key in SecuritySubPluginNames]: string[] } = { + detections: [ + i18n.translate('xpack.securitySolution.search.detections', { + defaultMessage: 'Detections', + }), + ], + hosts: [ + i18n.translate('xpack.securitySolution.search.hosts', { + defaultMessage: 'Hosts', + }), + ], + network: [ + i18n.translate('xpack.securitySolution.search.network', { + defaultMessage: 'Network', + }), + ], + timelines: [ + i18n.translate('xpack.securitySolution.search.timelines', { + defaultMessage: 'Timelines', + }), + ], + overview: [ + i18n.translate('xpack.securitySolution.search.overview', { + defaultMessage: 'Overview', + }), + ], + case: [ + i18n.translate('xpack.securitySolution.search.cases', { + defaultMessage: 'Cases', + }), + ], + administration: [ + i18n.translate('xpack.securitySolution.search.administration', { + defaultMessage: 'Endpoint Administration', + }), + ], +}; + +/** + * A function that generates a subPlugin's meta tag + * @param subPluginName SubPluginName of the app to retrieve meta information for. + * @param licenseType optional string for license level, if not provided basic is assumed. + */ +export function getSearchDeepLinksAndKeywords( + subPluginName: SecuritySubPluginNames, + licenseType?: LicenseType +): AppMeta { + const baseRoutes = [...securityDeepLinks[subPluginName].base]; + if ( + licenseType === 'gold' || + licenseType === 'platinum' || + licenseType === 'enterprise' || + licenseType === 'trial' + ) { + const premiumRoutes = + securityDeepLinks[subPluginName] && securityDeepLinks[subPluginName].premium; + if (premiumRoutes !== undefined) { + return { + keywords: subpluginKeywords[subPluginName], + searchDeepLinks: [...baseRoutes, ...premiumRoutes], + }; + } + } + return { + keywords: subpluginKeywords[subPluginName], + searchDeepLinks: baseRoutes, + }; +} +/** + * A function that updates a subplugin's meta property as appropriate when license level changes. + * @param subPluginName SubPluginName of the app to register searchDeepLinks for + * @param appUpdater an instance of appUpdater$ observable to update search links when needed. + * @param licenseType A string representing the current license level. + */ +export function registerSearchLinks( + subPluginName: SecuritySubPluginNames, + appUpdater?: Subject, + licenseType?: LicenseType +) { + if (appUpdater !== undefined) { + appUpdater.next(() => ({ + meta: getSearchDeepLinksAndKeywords(subPluginName, licenseType), + })); + } +} diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index 4d1c091fdaa5fd..95e64fe37d3331 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -17,7 +17,7 @@ import { CombinedState, } from 'redux'; -import { AppMountParameters } from '../../../../../src/core/public'; +import { AppMountParameters, AppSearchDeepLink } from '../../../../../src/core/public'; import { StartServices } from '../types'; import { AppFrontendLibs } from '../common/lib/lib'; @@ -34,6 +34,7 @@ import { State, SubPluginsInitReducer } from '../common/store'; import { Immutable } from '../../common/endpoint/types'; import { AppAction } from '../common/store/actions'; import { TimelineState } from '../timelines/store/timeline/types'; +import { SecurityPageName } from '../../common/constants'; export { SecurityPageName } from '../../common/constants'; export interface SecuritySubPluginStore { @@ -55,6 +56,15 @@ export type SecuritySubPluginKeyStore = | 'alertList' | 'management'; +export type SecuritySubPluginNames = keyof typeof SecurityPageName; + +interface SecurityDeepLink { + base: AppSearchDeepLink[]; + premium?: AppSearchDeepLink[]; +} + +export type SecurityDeepLinks = { [key in SecuritySubPluginNames]: SecurityDeepLink }; + /** * Returned by the various 'SecuritySubPlugin' classes from the `start` method. */ diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 1b7b7059cbafce..e8997cddc2cdcd 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Subject, Subscription } from 'rxjs'; import { pluck } from 'rxjs/operators'; import { PluginSetup, @@ -19,6 +19,7 @@ import { } from './types'; import { AppMountParameters, + AppUpdater, CoreSetup, CoreStart, PluginInitializerContext, @@ -46,6 +47,7 @@ import { } from '../common/constants'; import { SecurityPageName } from './app/types'; +import { registerSearchLinks, getSearchDeepLinksAndKeywords } from './app/search'; import { manageOldSiemRoutes } from './helpers'; import { OVERVIEW, @@ -73,8 +75,12 @@ export class Plugin implements IPlugin(); + private hostsUpdater$ = new Subject(); + private networkUpdater$ = new Subject(); private storage = new Storage(localStorage); + private licensingSubscription: Subscription | null = null; /** * Lazily instantiated subPlugins. @@ -184,6 +190,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { detections: subPlugin } = await this.subPlugins(); @@ -206,6 +213,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { hosts: subPlugin } = await this.subPlugins(); @@ -227,6 +235,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { network: subPlugin } = await this.subPlugins(); @@ -248,6 +257,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { timelines: subPlugin } = await this.subPlugins(); @@ -356,11 +366,30 @@ export class Plugin implements IPlugin { + if (currentLicense.type !== undefined) { + registerSearchLinks(SecurityPageName.network, this.networkUpdater$, currentLicense.type); + registerSearchLinks( + SecurityPageName.detections, + this.detectionsUpdater$, + currentLicense.type + ); + registerSearchLinks(SecurityPageName.hosts, this.hostsUpdater$, currentLicense.type); + } + }); + } return {}; } public stop() { + if (this.licensingSubscription !== null) { + this.licensingSubscription.unsubscribe(); + } return {}; } diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 7a9e9f07b20627..e88077679e1b62 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -34,10 +34,11 @@ import { Network } from './network'; import { Overview } from './overview'; import { Timelines } from './timelines'; import { Management } from './management'; -import { LicensingPluginStart } from '../../licensing/public'; +import { LicensingPluginStart, LicensingPluginSetup } from '../../licensing/public'; export interface SetupPlugins { home?: HomePublicPluginSetup; + licensing: LicensingPluginSetup; security: SecurityPluginSetup; triggersActionsUi: TriggersActionsSetup; usageCollection?: UsageCollectionSetup; From 5b0e283bcc8d0e8ddc851b9d53d4148b961509da Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Wed, 17 Feb 2021 23:07:26 -0500 Subject: [PATCH 24/93] [Security Solution][Detections][Threshold Rules] Threshold multiple aggregations with cardinality (#90826) * Remove unnecessary spreads * Layout, round 1 * Revert "Layout, round 1" This reverts commit b73b34acd5efe7a8b3632ddaa27070bc403fa1b4. * Make threshold field an array * Add cardinality fields * Fix validation schema * Query for multi-aggs * Finish multi-agg aggregation * Translate to multi-agg buckets * Fix existing tests and add new test skeletons * clean up * Fix types * Fix threshold_result data structure * previous signals filter * Fix previous signal detection * Finish previous signal parsing * tying up loose ends * Fix timeline view for multi-agg threshold signals * Fix build_bulk_body tests * test fixes * Add test for threshold bucket filters * Address comments * Fixing schema errors * Remove unnecessary comment * Fix tests * Fix types * linting * linting * Fixes * Handle pre-7.12 threshold format in timeline view * missing null check * adding in follow-up pr * Handle pre-7.12 filters * unnecessary change * Revert "unnecessary change" This reverts commit 3edc7f2f2ad41d68dde7e53df3b5aef143554215. * linting * Fix rule schemas * Fix tests Co-authored-by: Marshall Main --- .../plugins/osquery/common/ecs/rule/index.ts | 2 +- .../schemas/common/schemas.ts | 21 +- .../common/ecs/rule/index.ts | 5 +- .../common/ecs/signal/index.ts | 1 + .../matrix_histogram/index.ts | 9 +- .../indicator_match_rule.spec.ts | 614 +++++++++--------- .../detection_rules/threshold_rule.spec.ts | 179 ++--- .../components/matrix_histogram/types.ts | 9 +- .../components/alerts_table/actions.tsx | 102 ++- .../rules/query_preview/index.test.tsx | 28 +- .../components/rules/query_preview/index.tsx | 9 +- .../rules/query_preview/reducer.test.ts | 42 +- .../components/rules/query_preview/reducer.ts | 5 +- .../rules/step_define_rule/index.tsx | 50 +- .../rules/step_define_rule/schema.tsx | 51 +- .../rules/threshold_input/index.tsx | 87 ++- .../rules/all/__mocks__/mock.ts | 6 +- .../detection_engine/rules/create/helpers.ts | 4 +- .../detection_engine/rules/helpers.test.tsx | 6 + .../pages/detection_engine/rules/helpers.tsx | 12 +- .../pages/detection_engine/rules/types.ts | 4 +- .../routes/index/signals_mapping.json | 23 +- .../signals/__mocks__/es_results.ts | 94 +++ .../signals/build_bulk_body.test.ts | 36 +- .../detection_engine/signals/build_signal.ts | 13 +- .../bulk_create_threshold_signals.test.ts | 58 +- .../signals/bulk_create_threshold_signals.ts | 155 ++++- .../signals/find_threshold_signals.ts | 79 ++- .../signals/signal_params_schema.ts | 7 +- .../signals/signal_rule_alert_type.ts | 6 +- .../threshold_find_previous_signals.ts | 35 +- .../threshold_get_bucket_filters.test.ts | 85 +++ .../signals/threshold_get_bucket_filters.ts | 95 ++- .../lib/detection_engine/signals/types.ts | 48 +- .../detection_engine/signals/utils.test.ts | 4 +- .../lib/detection_engine/signals/utils.ts | 28 +- .../security_solution/server/lib/types.ts | 12 +- .../timeline/factory/events/all/constants.ts | 1 + .../factory/events/all/helpers.test.ts | 1 + 39 files changed, 1429 insertions(+), 597 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.test.ts diff --git a/x-pack/plugins/osquery/common/ecs/rule/index.ts b/x-pack/plugins/osquery/common/ecs/rule/index.ts index 3a764a836e9b34..51d7722a3ecdec 100644 --- a/x-pack/plugins/osquery/common/ecs/rule/index.ts +++ b/x-pack/plugins/osquery/common/ecs/rule/index.ts @@ -28,7 +28,7 @@ export interface RuleEcs { tags?: string[]; threat?: unknown; threshold?: { - field: string; + field: string | string[]; value: number; }; type?: string[]; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index aade8be4f503fb..d97820f010a802 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -459,12 +459,21 @@ export type Threats = t.TypeOf; export const threatsOrUndefined = t.union([threats, t.undefined]); export type ThreatsOrUndefined = t.TypeOf; -export const threshold = t.exact( - t.type({ - field: t.string, - value: PositiveIntegerGreaterThanZero, - }) -); +export const threshold = t.intersection([ + t.exact( + t.type({ + field: t.union([t.string, t.array(t.string)]), + value: PositiveIntegerGreaterThanZero, + }) + ), + t.exact( + t.partial({ + cardinality_field: t.union([t.string, t.array(t.string), t.undefined, t.null]), + cardinality_value: t.union([PositiveInteger, t.undefined, t.null]), // TODO: cardinality_value should be set if cardinality_field is set + }) + ), +]); +// TODO: codec to transform threshold field string to string[] ? export type Threshold = t.TypeOf; export const thresholdOrUndefined = t.union([threshold, t.undefined]); diff --git a/x-pack/plugins/security_solution/common/ecs/rule/index.ts b/x-pack/plugins/security_solution/common/ecs/rule/index.ts index 3a764a836e9b34..5463b21f6b7f70 100644 --- a/x-pack/plugins/security_solution/common/ecs/rule/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/rule/index.ts @@ -27,10 +27,7 @@ export interface RuleEcs { severity?: string[]; tags?: string[]; threat?: unknown; - threshold?: { - field: string; - value: number; - }; + threshold?: unknown; type?: string[]; size?: string[]; to?: string[]; diff --git a/x-pack/plugins/security_solution/common/ecs/signal/index.ts b/x-pack/plugins/security_solution/common/ecs/signal/index.ts index eb5e629a1abcf7..45e1f04d2b405f 100644 --- a/x-pack/plugins/security_solution/common/ecs/signal/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/signal/index.ts @@ -14,4 +14,5 @@ export interface SignalEcs { group?: { id?: string[]; }; + threshold_result?: unknown; } diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts index df6543ddbce903..c71108d58d980f 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts @@ -36,7 +36,14 @@ export interface MatrixHistogramRequestOptions extends RequestBasicOptions { timerange: TimerangeInput; histogramType: MatrixHistogramType; stackByField: string; - threshold?: { field: string | undefined; value: number } | undefined; + threshold?: + | { + field: string | string[] | undefined; + value: number; + cardinality_field?: string | undefined; + cardinality_value?: number | undefined; + } + | undefined; inspect?: Maybe; isPtrIncluded?: boolean; } diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts index a69f808001800d..bc52be678347aa 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts @@ -98,359 +98,375 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { DETECTIONS_URL, RULE_CREATION } from '../../urls/navigation'; -describe('Detection rules, Indicator Match', () => { - const expectedUrls = newThreatIndicatorRule.referenceUrls.join(''); - const expectedFalsePositives = newThreatIndicatorRule.falsePositivesExamples.join(''); - const expectedTags = newThreatIndicatorRule.tags.join(''); - const expectedMitre = formatMitreAttackDescription(newThreatIndicatorRule.mitre); - const expectedNumberOfRules = 1; - const expectedNumberOfAlerts = 1; - - before(() => { - cleanKibana(); - esArchiverLoad('threat_indicator'); - esArchiverLoad('threat_data'); - }); - after(() => { - esArchiverUnload('threat_indicator'); - esArchiverUnload('threat_data'); - }); - - describe('Creating new indicator match rules', () => { - beforeEach(() => { - loginAndWaitForPageWithoutDateRange(RULE_CREATION); - selectIndicatorMatchType(); +// Skipped for 7.12 FF - flaky tests +describe.skip('indicator match', () => { + describe('Detection rules, Indicator Match', () => { + const expectedUrls = newThreatIndicatorRule.referenceUrls.join(''); + const expectedFalsePositives = newThreatIndicatorRule.falsePositivesExamples.join(''); + const expectedTags = newThreatIndicatorRule.tags.join(''); + const expectedMitre = formatMitreAttackDescription(newThreatIndicatorRule.mitre); + const expectedNumberOfRules = 1; + const expectedNumberOfAlerts = 1; + + before(() => { + cleanKibana(); + esArchiverLoad('threat_indicator'); + esArchiverLoad('threat_data'); }); - - describe('Index patterns', () => { - it('Contains a predefined index pattern', () => { - getIndicatorIndex().should('have.text', indexPatterns.join('')); - }); - - it('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => { - getIndicatorIndicatorIndex().type(`${newThreatIndicatorRule.indicatorIndexPattern}{enter}`); - getDefineContinueButton().click(); - getIndexPatternInvalidationText().should('not.exist'); - }); - - it('Shows invalidation text when you try to continue without filling it out', () => { - getIndexPatternClearButton().click(); - getIndicatorIndicatorIndex().type(`${newThreatIndicatorRule.indicatorIndexPattern}{enter}`); - getDefineContinueButton().click(); - getIndexPatternInvalidationText().should('exist'); - }); + after(() => { + esArchiverUnload('threat_indicator'); + esArchiverUnload('threat_data'); }); - describe('Indicator index patterns', () => { - it('Contains empty index pattern', () => { - getIndicatorIndicatorIndex().should('have.text', ''); - }); - - it('Does NOT show invalidation text on initial page load', () => { - getIndexPatternInvalidationText().should('not.exist'); - }); - - it('Shows invalidation text if you try to continue without filling it out', () => { - getDefineContinueButton().click(); - getIndexPatternInvalidationText().should('exist'); + describe('Creating new indicator match rules', () => { + beforeEach(() => { + loginAndWaitForPageWithoutDateRange(RULE_CREATION); + selectIndicatorMatchType(); }); - }); - describe('custom query input', () => { - it('Has a default set of *:*', () => { - getCustomQueryInput().should('have.text', '*:*'); - }); + describe('Index patterns', () => { + it('Contains a predefined index pattern', () => { + getIndicatorIndex().should('have.text', indexPatterns.join('')); + }); - it('Shows invalidation text if text is removed', () => { - getCustomQueryInput().type('{selectall}{del}'); - getCustomQueryInvalidationText().should('exist'); - }); - }); + it('Does NOT show invalidation text on initial page load if indicator index pattern is filled out', () => { + getIndicatorIndicatorIndex().type( + `${newThreatIndicatorRule.indicatorIndexPattern}{enter}` + ); + getDefineContinueButton().click(); + getIndexPatternInvalidationText().should('not.exist'); + }); - describe('custom indicator query input', () => { - it('Has a default set of *:*', () => { - getCustomIndicatorQueryInput().should('have.text', '*:*'); + it('Shows invalidation text when you try to continue without filling it out', () => { + getIndexPatternClearButton().click(); + getIndicatorIndicatorIndex().type( + `${newThreatIndicatorRule.indicatorIndexPattern}{enter}` + ); + getDefineContinueButton().click(); + getIndexPatternInvalidationText().should('exist'); + }); }); - it('Shows invalidation text if text is removed', () => { - getCustomIndicatorQueryInput().type('{selectall}{del}'); - getCustomQueryInvalidationText().should('exist'); - }); - }); + describe('Indicator index patterns', () => { + it('Contains empty index pattern', () => { + getIndicatorIndicatorIndex().should('have.text', ''); + }); - describe('Indicator mapping', () => { - beforeEach(() => { - fillIndexAndIndicatorIndexPattern( - newThreatIndicatorRule.index, - newThreatIndicatorRule.indicatorIndexPattern - ); - }); + it('Does NOT show invalidation text on initial page load', () => { + getIndexPatternInvalidationText().should('not.exist'); + }); - it('Does NOT show invalidation text on initial page load', () => { - getIndicatorInvalidationText().should('not.exist'); + it('Shows invalidation text if you try to continue without filling it out', () => { + getDefineContinueButton().click(); + getIndexPatternInvalidationText().should('exist'); + }); }); - it('Shows invalidation text when you try to press continue without filling anything out', () => { - getDefineContinueButton().click(); - getIndicatorAtLeastOneInvalidationText().should('exist'); - }); + describe('custom query input', () => { + it('Has a default set of *:*', () => { + getCustomQueryInput().should('have.text', '*:*'); + }); - it('Shows invalidation text when the "AND" button is pressed and both the mappings are blank', () => { - getIndicatorAndButton().click(); - getIndicatorInvalidationText().should('exist'); + it('Shows invalidation text if text is removed', () => { + getCustomQueryInput().type('{selectall}{del}'); + getCustomQueryInvalidationText().should('exist'); + }); }); - it('Shows invalidation text when the "OR" button is pressed and both the mappings are blank', () => { - getIndicatorOrButton().click(); - getIndicatorInvalidationText().should('exist'); - }); + describe('custom indicator query input', () => { + it('Has a default set of *:*', () => { + getCustomIndicatorQueryInput().should('have.text', '*:*'); + }); - it('Does NOT show invalidation text when there is a valid "index field" and a valid "indicator index field"', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + it('Shows invalidation text if text is removed', () => { + getCustomIndicatorQueryInput().type('{selectall}{del}'); + getCustomQueryInvalidationText().should('exist'); }); - getDefineContinueButton().click(); - getIndicatorInvalidationText().should('not.exist'); }); - it('Shows invalidation text when there is an invalid "index field" and a valid "indicator index field"', () => { - fillIndicatorMatchRow({ - indexField: 'non-existent-value', - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, - validColumns: 'indicatorField', + describe('Indicator mapping', () => { + beforeEach(() => { + fillIndexAndIndicatorIndexPattern( + newThreatIndicatorRule.index, + newThreatIndicatorRule.indicatorIndexPattern + ); }); - getDefineContinueButton().click(); - getIndicatorInvalidationText().should('exist'); - }); - it('Shows invalidation text when there is a valid "index field" and an invalid "indicator index field"', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: 'non-existent-value', - validColumns: 'indexField', + it('Does NOT show invalidation text on initial page load', () => { + getIndicatorInvalidationText().should('not.exist'); }); - getDefineContinueButton().click(); - getIndicatorInvalidationText().should('exist'); - }); - it('Deletes the first row when you have two rows. Both rows valid rows of "index fields" and valid "indicator index fields". The second row should become the first row', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + it('Shows invalidation text when you try to press continue without filling anything out', () => { + getDefineContinueButton().click(); + getIndicatorAtLeastOneInvalidationText().should('exist'); }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: 'agent.name', - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, - validColumns: 'indicatorField', + + it('Shows invalidation text when the "AND" button is pressed and both the mappings are blank', () => { + getIndicatorAndButton().click(); + getIndicatorInvalidationText().should('exist'); }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField().should('have.text', 'agent.name'); - getIndicatorMappingComboField().should( - 'have.text', - newThreatIndicatorRule.indicatorIndexField - ); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); - it('Deletes the first row when you have two rows. Both rows have valid "index fields" and invalid "indicator index fields". The second row should become the first row', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: 'non-existent-value', - validColumns: 'indexField', + it('Shows invalidation text when the "OR" button is pressed and both the mappings are blank', () => { + getIndicatorOrButton().click(); + getIndicatorInvalidationText().should('exist'); }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: 'second-non-existent-value', - validColumns: 'indexField', + + it('Does NOT show invalidation text when there is a valid "index field" and a valid "indicator index field"', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getDefineContinueButton().click(); + getIndicatorInvalidationText().should('not.exist'); }); - getIndicatorDeleteButton().click(); - getIndicatorMappingComboField().should('have.text', 'second-non-existent-value'); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); - it('Deletes the first row when you have two rows. Both rows have valid "indicator index fields" and invalid "index fields". The second row should become the first row', () => { - fillIndicatorMatchRow({ - indexField: 'non-existent-value', - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, - validColumns: 'indicatorField', + it('Shows invalidation text when there is an invalid "index field" and a valid "indicator index field"', () => { + fillIndicatorMatchRow({ + indexField: 'non-existent-value', + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + validColumns: 'indicatorField', + }); + getDefineContinueButton().click(); + getIndicatorInvalidationText().should('exist'); }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: 'second-non-existent-value', - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, - validColumns: 'indicatorField', + + it('Shows invalidation text when there is a valid "index field" and an invalid "indicator index field"', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: 'non-existent-value', + validColumns: 'indexField', + }); + getDefineContinueButton().click(); + getIndicatorInvalidationText().should('exist'); }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField().should('have.text', 'second-non-existent-value'); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); - it('Deletes the first row of data but not the UI elements and the text defaults back to the placeholder of Search', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + it('Deletes the first row when you have two rows. Both rows valid rows of "index fields" and valid "indicator index fields". The second row should become the first row', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: 'agent.name', + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + validColumns: 'indicatorField', + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField().should('have.text', 'agent.name'); + getIndicatorMappingComboField().should( + 'have.text', + newThreatIndicatorRule.indicatorIndexField + ); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField().should('text', 'Search'); - getIndicatorMappingComboField().should('text', 'Search'); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); - }); - it('Deletes the second row when you have three rows. The first row is valid data, the second row is invalid data, and the third row is valid data. Third row should shift up correctly', () => { - fillIndicatorMatchRow({ - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + it('Deletes the first row when you have two rows. Both rows have valid "index fields" and invalid "indicator index fields". The second row should become the first row', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: 'non-existent-value', + validColumns: 'indexField', + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: 'second-non-existent-value', + validColumns: 'indexField', + }); + getIndicatorDeleteButton().click(); + getIndicatorMappingComboField().should('have.text', 'second-non-existent-value'); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: 'non-existent-value', - indicatorIndexField: 'non-existent-value', - validColumns: 'none', + + it('Deletes the first row when you have two rows. Both rows have valid "indicator index fields" and invalid "index fields". The second row should become the first row', () => { + fillIndicatorMatchRow({ + indexField: 'non-existent-value', + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + validColumns: 'indicatorField', + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: 'second-non-existent-value', + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + validColumns: 'indicatorField', + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField().should('have.text', 'second-non-existent-value'); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); }); - getIndicatorAndButton().click(); - fillIndicatorMatchRow({ - rowNumber: 3, - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + + it('Deletes the first row of data but not the UI elements and the text defaults back to the placeholder of Search', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField().should('text', 'Search'); + getIndicatorMappingComboField().should('text', 'Search'); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); }); - getIndicatorDeleteButton(2).click(); - getIndicatorIndexComboField(1).should('text', newThreatIndicatorRule.indicatorMapping); - getIndicatorMappingComboField(1).should('text', newThreatIndicatorRule.indicatorIndexField); - getIndicatorIndexComboField(2).should('text', newThreatIndicatorRule.indicatorMapping); - getIndicatorMappingComboField(2).should('text', newThreatIndicatorRule.indicatorIndexField); - getIndicatorIndexComboField(3).should('not.exist'); - getIndicatorMappingComboField(3).should('not.exist'); - }); - it('Can add two OR rows and delete the second row. The first row has invalid data and the second row has valid data. The first row is deleted and the second row shifts up correctly.', () => { - fillIndicatorMatchRow({ - indexField: 'non-existent-value-one', - indicatorIndexField: 'non-existent-value-two', - validColumns: 'none', + it('Deletes the second row when you have three rows. The first row is valid data, the second row is invalid data, and the third row is valid data. Third row should shift up correctly', () => { + fillIndicatorMatchRow({ + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: 'non-existent-value', + indicatorIndexField: 'non-existent-value', + validColumns: 'none', + }); + getIndicatorAndButton().click(); + fillIndicatorMatchRow({ + rowNumber: 3, + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getIndicatorDeleteButton(2).click(); + getIndicatorIndexComboField(1).should('text', newThreatIndicatorRule.indicatorMapping); + getIndicatorMappingComboField(1).should( + 'text', + newThreatIndicatorRule.indicatorIndexField + ); + getIndicatorIndexComboField(2).should('text', newThreatIndicatorRule.indicatorMapping); + getIndicatorMappingComboField(2).should( + 'text', + newThreatIndicatorRule.indicatorIndexField + ); + getIndicatorIndexComboField(3).should('not.exist'); + getIndicatorMappingComboField(3).should('not.exist'); }); - getIndicatorOrButton().click(); - fillIndicatorMatchRow({ - rowNumber: 2, - indexField: newThreatIndicatorRule.indicatorMapping, - indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + + it('Can add two OR rows and delete the second row. The first row has invalid data and the second row has valid data. The first row is deleted and the second row shifts up correctly.', () => { + fillIndicatorMatchRow({ + indexField: 'non-existent-value-one', + indicatorIndexField: 'non-existent-value-two', + validColumns: 'none', + }); + getIndicatorOrButton().click(); + fillIndicatorMatchRow({ + rowNumber: 2, + indexField: newThreatIndicatorRule.indicatorMapping, + indicatorIndexField: newThreatIndicatorRule.indicatorIndexField, + }); + getIndicatorDeleteButton().click(); + getIndicatorIndexComboField().should('text', newThreatIndicatorRule.indicatorMapping); + getIndicatorMappingComboField().should( + 'text', + newThreatIndicatorRule.indicatorIndexField + ); + getIndicatorIndexComboField(2).should('not.exist'); + getIndicatorMappingComboField(2).should('not.exist'); }); - getIndicatorDeleteButton().click(); - getIndicatorIndexComboField().should('text', newThreatIndicatorRule.indicatorMapping); - getIndicatorMappingComboField().should('text', newThreatIndicatorRule.indicatorIndexField); - getIndicatorIndexComboField(2).should('not.exist'); - getIndicatorMappingComboField(2).should('not.exist'); }); }); - }); - describe('Generating signals', () => { - beforeEach(() => { - cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); - waitForAlertsPanelToBeLoaded(); - waitForAlertsIndexToBeCreated(); - goToManageAlertsDetectionRules(); - waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); - goToCreateNewRule(); - selectIndicatorMatchType(); - }); + describe('Generating signals', () => { + beforeEach(() => { + cleanKibana(); + loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertsDetectionRules(); + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); + goToCreateNewRule(); + selectIndicatorMatchType(); + }); - it('Creates and activates a new Indicator Match rule', () => { - fillDefineIndicatorMatchRuleAndContinue(newThreatIndicatorRule); - fillAboutRuleAndContinue(newThreatIndicatorRule); - fillScheduleRuleAndContinue(newThreatIndicatorRule); - createAndActivateRule(); + it('Creates and activates a new Indicator Match rule', () => { + fillDefineIndicatorMatchRuleAndContinue(newThreatIndicatorRule); + fillAboutRuleAndContinue(newThreatIndicatorRule); + fillScheduleRuleAndContinue(newThreatIndicatorRule); + createAndActivateRule(); - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); + cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - changeToThreeHundredRowsPerPage(); - waitForRulesToBeLoaded(); + changeToThreeHundredRowsPerPage(); + waitForRulesToBeLoaded(); - cy.get(RULES_TABLE).then(($table) => { - cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); - }); + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); + }); - filterByCustomRules(); + filterByCustomRules(); - cy.get(RULES_TABLE).then(($table) => { - cy.wrap($table.find(RULES_ROW).length).should('eql', 1); - }); - cy.get(RULE_NAME).should('have.text', newThreatIndicatorRule.name); - cy.get(RISK_SCORE).should('have.text', newThreatIndicatorRule.riskScore); - cy.get(SEVERITY).should('have.text', newThreatIndicatorRule.severity); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetails(); - - cy.get(RULE_NAME_HEADER).should('have.text', `${newThreatIndicatorRule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', newThreatIndicatorRule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', newThreatIndicatorRule.severity); - getDetails(RISK_SCORE_DETAILS).should('have.text', newThreatIndicatorRule.riskScore); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', 1); }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + cy.get(RULE_NAME).should('have.text', newThreatIndicatorRule.name); + cy.get(RISK_SCORE).should('have.text', newThreatIndicatorRule.riskScore); + cy.get(SEVERITY).should('have.text', newThreatIndicatorRule.severity); + cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); + + goToRuleDetails(); + + cy.get(RULE_NAME_HEADER).should('have.text', `${newThreatIndicatorRule.name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', newThreatIndicatorRule.description); + cy.get(ABOUT_DETAILS).within(() => { + getDetails(SEVERITY_DETAILS).should('have.text', newThreatIndicatorRule.severity); + getDetails(RISK_SCORE_DETAILS).should('have.text', newThreatIndicatorRule.riskScore); + getDetails(REFERENCE_URLS_DETAILS).should((details) => { + expect(removeExternalLinkText(details.text())).equal(expectedUrls); + }); + getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); + getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { + expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + }); + getDetails(TAGS_DETAILS).should('have.text', expectedTags); + }); + cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); + cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); + + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(INDEX_PATTERNS_DETAILS).should( + 'have.text', + newThreatIndicatorRule.index!.join('') + ); + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', '*:*'); + getDetails(RULE_TYPE_DETAILS).should('have.text', 'Indicator Match'); + getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); + getDetails(INDICATOR_INDEX_PATTERNS).should( + 'have.text', + newThreatIndicatorRule.indicatorIndexPattern.join('') + ); + getDetails(INDICATOR_MAPPING).should( + 'have.text', + `${newThreatIndicatorRule.indicatorMapping} MATCHES ${newThreatIndicatorRule.indicatorIndexField}` + ); + getDetails(INDICATOR_INDEX_QUERY).should('have.text', '*:*'); }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); - - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should( - 'have.text', - newThreatIndicatorRule.index!.join('') - ); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', '*:*'); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Indicator Match'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - getDetails(INDICATOR_INDEX_PATTERNS).should( - 'have.text', - newThreatIndicatorRule.indicatorIndexPattern.join('') - ); - getDetails(INDICATOR_MAPPING).should( - 'have.text', - `${newThreatIndicatorRule.indicatorMapping} MATCHES ${newThreatIndicatorRule.indicatorIndexField}` - ); - getDetails(INDICATOR_INDEX_QUERY).should('have.text', '*:*'); - }); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS).should( - 'have.text', - `${newThreatIndicatorRule.runsEvery.interval}${newThreatIndicatorRule.runsEvery.type}` - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( - 'have.text', - `${newThreatIndicatorRule.lookBack.interval}${newThreatIndicatorRule.lookBack.type}` - ); - }); + cy.get(SCHEDULE_DETAILS).within(() => { + getDetails(RUNS_EVERY_DETAILS).should( + 'have.text', + `${newThreatIndicatorRule.runsEvery.interval}${newThreatIndicatorRule.runsEvery.type}` + ); + getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( + 'have.text', + `${newThreatIndicatorRule.lookBack.interval}${newThreatIndicatorRule.lookBack.type}` + ); + }); - waitForTheRuleToBeExecuted(); - waitForAlertsToPopulate(); - - cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts); - cy.get(ALERT_RULE_NAME).first().should('have.text', newThreatIndicatorRule.name); - cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); - cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threat_match'); - cy.get(ALERT_RULE_SEVERITY) - .first() - .should('have.text', newThreatIndicatorRule.severity.toLowerCase()); - cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', newThreatIndicatorRule.riskScore); + waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); + + cy.get(NUMBER_OF_ALERTS).should('have.text', expectedNumberOfAlerts); + cy.get(ALERT_RULE_NAME).first().should('have.text', newThreatIndicatorRule.name); + cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); + cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threat_match'); + cy.get(ALERT_RULE_SEVERITY) + .first() + .should('have.text', newThreatIndicatorRule.severity.toLowerCase()); + cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', newThreatIndicatorRule.riskScore); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts index d93ed7a0e97a5c..3c188345111c85 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts @@ -80,101 +80,104 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { DETECTIONS_URL } from '../../urls/navigation'; -describe('Detection rules, threshold', () => { - const expectedUrls = newThresholdRule.referenceUrls.join(''); - const expectedFalsePositives = newThresholdRule.falsePositivesExamples.join(''); - const expectedTags = newThresholdRule.tags.join(''); - const expectedMitre = formatMitreAttackDescription(newThresholdRule.mitre); - - const rule = { ...newThresholdRule }; - - beforeEach(() => { - cleanKibana(); - createTimeline(newThresholdRule.timeline).then((response) => { - rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; +// Skipped until post-FF for 7.12 +describe.skip('Threshold Rules', () => { + describe('Detection rules, threshold', () => { + const expectedUrls = newThresholdRule.referenceUrls.join(''); + const expectedFalsePositives = newThresholdRule.falsePositivesExamples.join(''); + const expectedTags = newThresholdRule.tags.join(''); + const expectedMitre = formatMitreAttackDescription(newThresholdRule.mitre); + + const rule = { ...newThresholdRule }; + + beforeEach(() => { + cleanKibana(); + createTimeline(newThresholdRule.timeline).then((response) => { + rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; + }); }); - }); - it('Creates and activates a new threshold rule', () => { - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); - waitForAlertsPanelToBeLoaded(); - waitForAlertsIndexToBeCreated(); - goToManageAlertsDetectionRules(); - waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); - goToCreateNewRule(); - selectThresholdRuleType(); - fillDefineThresholdRuleAndContinue(rule); - fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); - createAndActivateRule(); - - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - - changeToThreeHundredRowsPerPage(); - waitForRulesToBeLoaded(); - - const expectedNumberOfRules = 1; - cy.get(RULES_TABLE).then(($table) => { - cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); - }); + it('Creates and activates a new threshold rule', () => { + loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertsDetectionRules(); + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); + goToCreateNewRule(); + selectThresholdRuleType(); + fillDefineThresholdRuleAndContinue(rule); + fillAboutRuleAndContinue(rule); + fillScheduleRuleAndContinue(rule); + createAndActivateRule(); + + cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); + + changeToThreeHundredRowsPerPage(); + waitForRulesToBeLoaded(); + + const expectedNumberOfRules = 1; + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules); + }); - filterByCustomRules(); + filterByCustomRules(); - cy.get(RULES_TABLE).then(($table) => { - cy.wrap($table.find(RULES_ROW).length).should('eql', 1); - }); - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.riskScore); - cy.get(SEVERITY).should('have.text', rule.severity); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetails(); - - cy.get(RULE_NAME_HEADER).should('have.text', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', rule.severity); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.riskScore); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); + cy.get(RULES_TABLE).then(($table) => { + cy.wrap($table.find(RULES_ROW).length).should('eql', 1); }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + cy.get(RULE_NAME).should('have.text', rule.name); + cy.get(RISK_SCORE).should('have.text', rule.riskScore); + cy.get(SEVERITY).should('have.text', rule.severity); + cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); + + goToRuleDetails(); + + cy.get(RULE_NAME_HEADER).should('have.text', `${rule.name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); + cy.get(ABOUT_DETAILS).within(() => { + getDetails(SEVERITY_DETAILS).should('have.text', rule.severity); + getDetails(RISK_SCORE_DETAILS).should('have.text', rule.riskScore); + getDetails(REFERENCE_URLS_DETAILS).should((details) => { + expect(removeExternalLinkText(details.text())).equal(expectedUrls); + }); + getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); + getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { + expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + }); + getDetails(TAGS_DETAILS).should('have.text', expectedTags); + }); + cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); + cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join('')); + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.customQuery); + getDetails(RULE_TYPE_DETAILS).should('have.text', 'Threshold'); + getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); + getDetails(THRESHOLD_DETAILS).should( + 'have.text', + `Results aggregated by ${rule.thresholdField} >= ${rule.threshold}` + ); + }); + cy.get(SCHEDULE_DETAILS).within(() => { + getDetails(RUNS_EVERY_DETAILS).should( + 'have.text', + `${rule.runsEvery.interval}${rule.runsEvery.type}` + ); + getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( + 'have.text', + `${rule.lookBack.interval}${rule.lookBack.type}` + ); }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join('')); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.customQuery); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'Threshold'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - getDetails(THRESHOLD_DETAILS).should( - 'have.text', - `Results aggregated by ${rule.thresholdField} >= ${rule.threshold}` - ); - }); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS).should( - 'have.text', - `${rule.runsEvery.interval}${rule.runsEvery.type}` - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( - 'have.text', - `${rule.lookBack.interval}${rule.lookBack.type}` - ); - }); - waitForTheRuleToBeExecuted(); - waitForAlertsToPopulate(); + waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); - cy.get(NUMBER_OF_ALERTS).should(($count) => expect(+$count.text()).to.be.lt(100)); - cy.get(ALERT_RULE_NAME).first().should('have.text', rule.name); - cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); - cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threshold'); - cy.get(ALERT_RULE_SEVERITY).first().should('have.text', rule.severity.toLowerCase()); - cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', rule.riskScore); + cy.get(NUMBER_OF_ALERTS).should(($count) => expect(+$count.text()).to.be.lt(100)); + cy.get(ALERT_RULE_NAME).first().should('have.text', rule.name); + cy.get(ALERT_RULE_VERSION).first().should('have.text', '1'); + cy.get(ALERT_RULE_METHOD).first().should('have.text', 'threshold'); + cy.get(ALERT_RULE_SEVERITY).first().should('have.text', rule.severity.toLowerCase()); + cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', rule.riskScore); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts index 0da881d2c6ea47..b993bcda56b8ed 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts @@ -74,7 +74,14 @@ export interface MatrixHistogramQueryProps { stackByField: string; startDate: string; histogramType: MatrixHistogramType; - threshold?: { field: string | undefined; value: number } | undefined; + threshold?: + | { + field: string | string[] | undefined; + value: number; + cardinality_field?: string | undefined; + cardinality_value?: number | undefined; + } + | undefined; skip?: boolean; isPtrIncluded?: boolean; } diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 14ccae250ac487..9f5ab7be8a117f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -8,7 +8,7 @@ /* eslint-disable complexity */ import dateMath from '@elastic/datemath'; -import { get, getOr, isEmpty, find } from 'lodash/fp'; +import { getOr, isEmpty } from 'lodash/fp'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; @@ -38,7 +38,10 @@ import { replaceTemplateFieldFromDataProviders, } from './helpers'; import { KueryFilterQueryKind } from '../../../common/store'; -import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; +import { + DataProvider, + QueryOperator, +} from '../../../timelines/components/timeline/data_providers/data_provider'; import { esFilters } from '../../../../../../../src/plugins/data/public'; export const getUpdateAlertsQuery = (eventIds: Readonly) => { @@ -47,7 +50,7 @@ export const getUpdateAlertsQuery = (eventIds: Readonly) => { bool: { filter: { terms: { - _id: [...eventIds], + _id: eventIds, }, }, }, @@ -148,35 +151,76 @@ export const getThresholdAggregationDataProvider = ( nonEcsData: TimelineNonEcsData[] ): DataProvider[] => { const thresholdEcsData: Ecs[] = Array.isArray(ecsData) ? ecsData : [ecsData]; - return thresholdEcsData.reduce((acc, tresholdData) => { - const aggregationField = tresholdData.signal?.rule?.threshold?.field!; - const aggregationValue = - get(aggregationField, tresholdData) ?? find(['field', aggregationField], nonEcsData)?.value; - const dataProviderValue = Array.isArray(aggregationValue) - ? aggregationValue[0] - : aggregationValue; - - if (!dataProviderValue) { - return acc; + return thresholdEcsData.reduce((outerAcc, thresholdData) => { + const threshold = thresholdData.signal?.rule?.threshold as string[]; + + let aggField: string[] = []; + let thresholdResult: { + terms?: Array<{ + field?: string; + value: string; + }>; + count: number; + }; + + try { + thresholdResult = JSON.parse((thresholdData.signal?.threshold_result as string[])[0]); + aggField = JSON.parse(threshold[0]).field; + } catch (err) { + thresholdResult = { + terms: [ + { + field: (thresholdData.rule?.threshold as { field: string }).field, + value: (thresholdData.signal?.threshold_result as { value: string }).value, + }, + ], + count: (thresholdData.signal?.threshold_result as { count: number }).count, + }; } - const aggregationFieldId = aggregationField.replace('.', '-'); + const aggregationFields = Array.isArray(aggField) ? aggField : [aggField]; return [ - ...acc, - { - and: [], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-${aggregationFieldId}-${dataProviderValue}`, - name: aggregationField, - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: aggregationField, - value: dataProviderValue, - operator: ':', - }, - }, + ...outerAcc, + ...aggregationFields.reduce((acc, aggregationField, i) => { + const aggregationValue = (thresholdResult.terms ?? []).filter( + (term: { field?: string | undefined; value: string }) => term.field === aggregationField + )[0].value; + const dataProviderValue = Array.isArray(aggregationValue) + ? aggregationValue[0] + : aggregationValue; + + if (!dataProviderValue) { + return acc; + } + + const aggregationFieldId = aggregationField.replace('.', '-'); + const dataProviderPartial = { + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-${aggregationFieldId}-${dataProviderValue}`, + name: aggregationField, + enabled: true, + excluded: false, + kqlQuery: '', + queryMatch: { + field: aggregationField, + value: dataProviderValue, + operator: ':' as QueryOperator, + }, + }; + + if (i === 0) { + return [ + ...acc, + { + ...dataProviderPartial, + and: [], + }, + ]; + } else { + acc[0].and.push(dataProviderPartial); + return acc; + } + }, []), ]; }, []); }; @@ -409,7 +453,7 @@ export const sendAlertToTimelineAction = async ({ ...timelineDefaults, description: `_id: ${ecsData._id}`, filters: getFiltersFromRule(ecsData.signal?.rule?.filters as string[]), - dataProviders: [...getThresholdAggregationDataProvider(ecs, nonEcsData)], + dataProviders: getThresholdAggregationDataProvider(ecsData, nonEcsData), id: TimelineId.active, indexNames: [], dateRange: { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx index e954f961e73362..bb87242d9bf108 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx @@ -288,7 +288,12 @@ describe('PreviewQuery', () => { idAria="queryPreview" query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} - threshold={{ field: 'agent.hostname', value: 200 }} + threshold={{ + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }} isDisabled={false} />
@@ -330,7 +335,12 @@ describe('PreviewQuery', () => { idAria="queryPreview" query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} - threshold={{ field: 'agent.hostname', value: 200 }} + threshold={{ + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }} isDisabled={false} /> @@ -369,7 +379,12 @@ describe('PreviewQuery', () => { idAria="queryPreview" query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} - threshold={{ field: undefined, value: 200 }} + threshold={{ + field: undefined, + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }} isDisabled={false} /> @@ -396,7 +411,12 @@ describe('PreviewQuery', () => { idAria="queryPreview" query={{ query: { query: 'file where true', language: 'kuery' }, filters: [] }} index={['foo-*']} - threshold={{ field: ' ', value: 200 }} + threshold={{ + field: ' ', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }} isDisabled={false} /> diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx index 6a11dcf316de31..377259fc9b212a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx @@ -56,7 +56,14 @@ export const initialState: State = { showNonEqlHistogram: false, }; -export type Threshold = { field: string | undefined; value: number } | undefined; +export type Threshold = + | { + field: string | string[] | undefined; + value: number; + cardinality_field: string | undefined; + cardinality_value: number | undefined; + } + | undefined; interface PreviewQueryProps { dataTestSubj: string; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts index ff90978dbb53f0..d1a9e5c5f768f4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts @@ -334,7 +334,12 @@ describe('queryPreviewReducer', () => { test('should set thresholdFieldExists to true if threshold field is defined and not empty string', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: 'agent.hostname', value: 200 }, + threshold: { + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'threshold', }); @@ -347,7 +352,12 @@ describe('queryPreviewReducer', () => { test('should set thresholdFieldExists to false if threshold field is not defined', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: undefined, value: 200 }, + threshold: { + field: undefined, + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'threshold', }); @@ -360,7 +370,12 @@ describe('queryPreviewReducer', () => { test('should set thresholdFieldExists to false if threshold field is empty string', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: ' ', value: 200 }, + threshold: { + field: ' ', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'threshold', }); @@ -373,7 +388,12 @@ describe('queryPreviewReducer', () => { test('should set showNonEqlHistogram to false if ruleType is eql', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: 'agent.hostname', value: 200 }, + threshold: { + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'eql', }); @@ -385,7 +405,12 @@ describe('queryPreviewReducer', () => { test('should set showNonEqlHistogram to true if ruleType is query', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: 'agent.hostname', value: 200 }, + threshold: { + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'query', }); @@ -397,7 +422,12 @@ describe('queryPreviewReducer', () => { test('should set showNonEqlHistogram to true if ruleType is saved_query', () => { const update = reducer(initialState, { type: 'setThresholdQueryVals', - threshold: { field: 'agent.hostname', value: 200 }, + threshold: { + field: 'agent.hostname', + value: 200, + cardinality_field: 'user.name', + cardinality_value: 2, + }, ruleType: 'saved_query', }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts index 84206b51272df0..2d301bf96122dd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts @@ -67,6 +67,7 @@ export type Action = type: 'setToFrom'; }; +/* eslint-disable-next-line complexity */ export const queryPreviewReducer = () => (state: State, action: Action): State => { switch (action.type) { case 'setQueryInfo': { @@ -131,7 +132,9 @@ export const queryPreviewReducer = () => (state: State, action: Action): State = const thresholdField = action.threshold != null && action.threshold.field != null && - action.threshold.field.trim() !== ''; + ((typeof action.threshold.field === 'string' && action.threshold.field.trim() !== '') || + (Array.isArray(action.threshold.field) && + action.threshold.field.every((field) => field.trim() !== ''))); const showNonEqlHist = action.ruleType === 'query' || action.ruleType === 'saved_query' || diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 5613d9bc1b4580..4c7a34dbdf0807 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -82,6 +82,8 @@ const stepDefineDefaultValue: DefineStepRule = { threshold: { field: [], value: '200', + cardinality_field: [], + cardinality_value: '2', }, timeline: { id: null, @@ -150,17 +152,30 @@ const StepDefineRuleComponent: FC = ({ ruleType: formRuleType, queryBar: formQuery, threatIndex: formThreatIndex, - 'threshold.value': formThresholdValue, 'threshold.field': formThresholdField, + 'threshold.value': formThresholdValue, + 'threshold.cardinality_field': formThresholdCardinalityField, + 'threshold.cardinality_value': formThresholdCardinalityValue, }, ] = useFormData< DefineStepRule & { - 'threshold.value': number | undefined; 'threshold.field': string[] | undefined; + 'threshold.value': number | undefined; + 'threshold.cardinality_field': string[] | undefined; + 'threshold.cardinality_value': number | undefined; } >({ form, - watch: ['index', 'ruleType', 'queryBar', 'threshold.value', 'threshold.field', 'threatIndex'], + watch: [ + 'index', + 'ruleType', + 'queryBar', + 'threshold.field', + 'threshold.value', + 'threshold.cardinality_field', + 'threshold.cardinality_value', + 'threatIndex', + ], }); const [isQueryBarValid, setIsQueryBarValid] = useState(false); const index = formIndex || initialState.index; @@ -274,17 +289,32 @@ const StepDefineRuleComponent: FC = ({ }, []); const thresholdFormValue = useMemo((): Threshold | undefined => { - return formThresholdValue != null && formThresholdField != null - ? { value: formThresholdValue, field: formThresholdField[0] } + return formThresholdValue != null && + formThresholdField != null && + formThresholdCardinalityField != null && + formThresholdCardinalityValue != null + ? { + field: formThresholdField[0], + value: formThresholdValue, + cardinality_field: formThresholdCardinalityField[0], + cardinality_value: formThresholdCardinalityValue, + } : undefined; - }, [formThresholdField, formThresholdValue]); + }, [ + formThresholdField, + formThresholdValue, + formThresholdCardinalityField, + formThresholdCardinalityValue, + ]); const ThresholdInputChildren = useCallback( - ({ thresholdField, thresholdValue }) => ( + ({ thresholdField, thresholdValue, thresholdCardinalityField, thresholdCardinalityValue }) => ( ), [aggregatableFields] @@ -429,6 +459,12 @@ const StepDefineRuleComponent: FC = ({ thresholdValue: { path: 'threshold.value', }, + thresholdCardinalityField: { + path: 'threshold.cardinality_field', + }, + thresholdCardinalityValue: { + path: 'threshold.cardinality_value', + }, }} > {ThresholdInputChildren} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx index 18f5008ff05f7c..a5352ede83d51d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx @@ -197,13 +197,13 @@ export const schema: FormSchema = { label: i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdFieldLabel', { - defaultMessage: 'Field', + defaultMessage: 'Group by', } ), helpText: i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdFieldHelpText', { - defaultMessage: 'Select a field to group results by', + defaultMessage: "Select fields to group by. Fields are joined together with 'AND'", } ), }, @@ -239,6 +239,53 @@ export const schema: FormSchema = { }, ], }, + cardinality_field: { + type: FIELD_TYPES.COMBO_BOX, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdCardinalityFieldLabel', + { + defaultMessage: 'Count', + } + ), + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdFieldCardinalityFieldHelpText', + { + defaultMessage: 'Select a field to check cardinality', + } + ), + }, + cardinality_value: { + type: FIELD_TYPES.NUMBER, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdCardinalityValueFieldLabel', + { + defaultMessage: 'Unique values', + } + ), + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ formData }] = args; + const needsValidation = isThresholdRule(formData.ruleType); + if (!needsValidation) { + return; + } + return fieldValidators.numberGreaterThanField({ + than: 1, + message: i18n.translate( + 'xpack.securitySolution.detectionEngine.validations.thresholdValueFieldData.numberGreaterThanOrEqualOneErrorMessage', + { + defaultMessage: 'Value must be greater than or equal to one.', + } + ), + allowEquality: true, + })(...args); + }, + }, + ], + }, }, threatIndex: { type: FIELD_TYPES.COMBO_BOX, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx index d473a83a9e35e0..287c99dce3e60a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/threshold_input/index.tsx @@ -19,11 +19,15 @@ const FIELD_COMBO_BOX_WIDTH = 410; export interface FieldValueThreshold { field: string[]; value: string; + cardinality_field: string[]; + cardinality_value: string; } interface ThresholdInputProps { thresholdField: FieldHook; thresholdValue: FieldHook; + thresholdCardinalityField: FieldHook; + thresholdCardinalityValue: FieldHook; browserFields: BrowserFields; } @@ -33,16 +37,19 @@ const OperatorWrapper = styled(EuiFlexItem)` const fieldDescribedByIds = ['detectionEngineStepDefineRuleThresholdField']; const valueDescribedByIds = ['detectionEngineStepDefineRuleThresholdValue']; +const cardinalityFieldDescribedByIds = ['detectionEngineStepDefineRuleThresholdCardinalityField']; +const cardinalityValueDescribedByIds = ['detectionEngineStepDefineRuleThresholdCardinalityValue']; const ThresholdInputComponent: React.FC = ({ thresholdField, thresholdValue, browserFields, + thresholdCardinalityField, + thresholdCardinalityValue, }: ThresholdInputProps) => { const fieldEuiFieldProps = useMemo( () => ({ fullWidth: true, - singleSelection: { asPlainText: true }, noSuggestions: false, options: getCategorizedFieldNames(browserFields), placeholder: THRESHOLD_FIELD_PLACEHOLDER, @@ -51,29 +58,65 @@ const ThresholdInputComponent: React.FC = ({ }), [browserFields] ); + const cardinalityFieldEuiProps = useMemo( + () => ({ + fullWidth: true, + noSuggestions: false, + options: getCategorizedFieldNames(browserFields), + placeholder: THRESHOLD_FIELD_PLACEHOLDER, + onCreateOption: undefined, + style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, + singleSelection: { asPlainText: true }, + }), + [browserFields] + ); return ( - - - - - {'>='} - - - + + + + + + {'>='} + + + + + + + + + {'>='} + + + + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts index 9853b8c6d34bc4..8bdbb1a74c73a1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -141,8 +141,10 @@ export const mockRuleWithEverything = (id: string): Rule => ({ type: 'saved_query', threat: getThreatMock(), threshold: { - field: 'host.name', + field: ['host.name'], value: 50, + cardinality_field: ['process.name'], + cardinality_value: 2, }, throttle: 'no_actions', timestamp_override: 'event.ingested', @@ -192,6 +194,8 @@ export const mockDefineStepRule = (): DefineStepRule => ({ threshold: { field: [''], value: '100', + cardinality_field: [''], + cardinality_value: '2', }, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts index 7c447214cfdebb..12e6d276c18d86 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts @@ -219,8 +219,10 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep saved_id: ruleFields.queryBar?.saved_id, ...(ruleType === 'threshold' && { threshold: { - field: ruleFields.threshold?.field[0] ?? '', + field: ruleFields.threshold?.field ?? [], value: parseInt(ruleFields.threshold?.value, 10) ?? 0, + cardinality_field: ruleFields.threshold.cardinality_field[0] ?? '', + cardinality_value: parseInt(ruleFields.threshold?.cardinality_value, 10) ?? 0, }, }), } diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx index f0511602bd67f7..29d1512030e74f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx @@ -84,6 +84,8 @@ describe('rule helpers', () => { threshold: { field: ['host.name'], value: '50', + cardinality_field: ['process.name'], + cardinality_value: '2', }, threatIndex: [], threatMapping: [], @@ -213,6 +215,8 @@ describe('rule helpers', () => { threshold: { field: [], value: '100', + cardinality_field: [], + cardinality_value: '0', }, threatIndex: [], threatMapping: [], @@ -255,6 +259,8 @@ describe('rule helpers', () => { threshold: { field: [], value: '100', + cardinality_field: [], + cardinality_value: '0', }, threatIndex: [], threatMapping: [], diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index c862d484b282b3..7c3930bb21d9a4 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -99,8 +99,18 @@ export const getDefineStepsData = (rule: Rule): DefineStepRule => ({ title: rule.timeline_title ?? null, }, threshold: { - field: rule.threshold?.field ? [rule.threshold.field] : [], + field: rule.threshold?.field + ? Array.isArray(rule.threshold.field) + ? rule.threshold.field + : [rule.threshold.field] + : [], value: `${rule.threshold?.value || 100}`, + cardinality_field: Array.isArray(rule.threshold?.cardinality_field) + ? rule.threshold!.cardinality_field + : rule.threshold?.cardinality_field != null + ? [rule.threshold!.cardinality_field] + : [], + cardinality_value: `${rule.threshold?.cardinality_value ?? 0}`, }, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index 94fdcc4069fc23..668ca556539ad0 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -158,8 +158,10 @@ export interface DefineStepRuleJson { query?: string; language?: string; threshold?: { - field: string; + field: string[]; value: number; + cardinality_field: string; + cardinality_value: number; }; threat_query?: string; threat_mapping?: ThreatMapping; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json index 909264c57067b4..22dba81e5c8e6a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json @@ -359,11 +359,28 @@ }, "threshold_result": { "properties": { + "terms": { + "properties": { + "field": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "cardinality": { + "properties": { + "field": { + "type": "keyword" + }, + "value": { + "type": "long" + } + } + }, "count": { "type": "long" - }, - "value": { - "type": "keyword" } } }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 6177fc4cd46614..977b38e59f8561 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -374,6 +374,100 @@ export const sampleSignalHit = (): SignalHit => ({ }, }); +export const sampleThresholdSignalHit = (): SignalHit => ({ + '@timestamp': '2020-04-20T21:27:45+0000', + event: { + kind: 'signal', + }, + signal: { + parents: [ + { + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + { + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + ancestors: [ + { + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + { + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + original_time: '2021-02-16T17:37:34.275Z', + status: 'open', + threshold_result: { + count: 72, + terms: [{ field: 'host.name', value: 'a hostname' }], + cardinality: [{ field: 'process.name', value: 6 }], + }, + rule: { + author: [], + id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', + created_at: '2020-04-20T21:27:45+0000', + updated_at: '2020-04-20T21:27:45+0000', + created_by: 'elastic', + description: 'some description', + enabled: true, + false_positives: ['false positive 1', 'false positive 2'], + from: 'now-6m', + immutable: false, + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + references: ['test 1', 'test 2'], + severity: 'high', + severity_mapping: [], + threshold: { + field: ['host.name'], + value: 5, + cardinality_field: 'process.name', + cardinality_value: 2, + }, + updated_by: 'elastic_kibana', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + threat: [], + version: 1, + status: 'succeeded', + status_date: '2020-02-22T16:47:50.047Z', + last_success_at: '2020-02-22T16:47:50.047Z', + last_success_message: 'succeeded', + output_index: '.siem-signals-default', + max_signals: 100, + risk_score: 55, + risk_score_mapping: [], + language: 'kuery', + rule_id: 'query-rule-id', + interval: '5m', + exceptions_list: getListArrayMock(), + }, + depth: 1, + }, +}); + +export const sampleWrappedThresholdSignalHit = (): WrappedSignalHit => { + return { + _index: 'myFakeSignalIndex', + _id: sampleIdGuid, + _source: sampleThresholdSignalHit(), + }; +}; + export const sampleBulkCreateDuplicateResult = { took: 60, errors: true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts index ca8fa821ce032c..362c368881b37f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -36,7 +36,13 @@ describe('buildBulkBody', () => { delete doc._source.source; const fakeSignalSourceHit = buildBulkBody({ doc, - ruleParams: sampleParams, + ruleParams: { + ...sampleParams, + threshold: { + field: ['host.name'], + value: 100, + }, + }, id: sampleRuleGuid, name: 'rule-name', actions: [], @@ -110,6 +116,10 @@ describe('buildBulkBody', () => { severity_mapping: [], tags: ['some fake tag 1', 'some fake tag 2'], threat: [], + threshold: { + field: ['host.name'], + value: 100, + }, throttle: 'no_actions', type: 'query', to: 'now', @@ -136,15 +146,25 @@ describe('buildBulkBody', () => { _source: { ...baseDoc._source, threshold_result: { + terms: [ + { + value: 'abcd', + }, + ], count: 5, - value: 'abcd', }, }, }; delete doc._source.source; const fakeSignalSourceHit = buildBulkBody({ doc, - ruleParams: sampleParams, + ruleParams: { + ...sampleParams, + threshold: { + field: [], + value: 4, + }, + }, id: sampleRuleGuid, name: 'rule-name', actions: [], @@ -218,6 +238,10 @@ describe('buildBulkBody', () => { severity_mapping: [], tags: ['some fake tag 1', 'some fake tag 2'], threat: [], + threshold: { + field: [], + value: 4, + }, throttle: 'no_actions', type: 'query', to: 'now', @@ -231,8 +255,12 @@ describe('buildBulkBody', () => { exceptions_list: getListArrayMock(), }, threshold_result: { + terms: [ + { + value: 'abcd', + }, + ], count: 5, - value: 'abcd', }, depth: 1, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts index cfbcfe5a04e59f..78ff0e8e1e5dd8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { SearchTypes } from '../../../../common/detection_engine/types'; import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { SIGNALS_TEMPLATE_VERSION } from '../routes/index/get_signals_template'; import { isEventTypeSignal } from './build_event_type_signal'; -import { Signal, Ancestor, BaseSignalHit } from './types'; +import { Signal, Ancestor, BaseSignalHit, ThresholdResult } from './types'; /** * Takes a parent signal or event document and extracts the information needed for the corresponding entry in the child @@ -95,16 +96,24 @@ export const buildSignal = (docs: BaseSignalHit[], rule: RulesSchema): Signal => }; }; +const isThresholdResult = (thresholdResult: SearchTypes): thresholdResult is ThresholdResult => { + return typeof thresholdResult === 'object'; +}; + /** * Creates signal fields that are only available in the special case where a signal has only 1 parent signal/event. * @param doc The parent signal/event of the new signal to be built. */ export const additionalSignalFields = (doc: BaseSignalHit) => { + const thresholdResult = doc._source.threshold_result; + if (thresholdResult != null && !isThresholdResult(thresholdResult)) { + throw new Error(`threshold_result failed to validate: ${thresholdResult}`); + } return { parent: buildParent(removeClashes(doc)), original_time: doc._source['@timestamp'], // This field has already been replaced with timestampOverride, if provided. original_event: doc._source.event ?? undefined, - threshold_result: doc._source.threshold_result, + threshold_result: thresholdResult, original_signal: doc._source.signal != null && !isEventTypeSignal(doc) ? doc._source.signal : undefined, }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts index 713178345361db..56d71048bb81b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts @@ -9,27 +9,41 @@ import { loggingSystemMock } from '../../../../../../../src/core/server/mocks'; import { sampleDocNoSortId, sampleDocSearchResultsNoSortId } from './__mocks__/es_results'; import { transformThresholdResultsToEcs } from './bulk_create_threshold_signals'; import { calculateThresholdSignalUuid } from './utils'; +import { Threshold } from '../../../../common/detection_engine/schemas/common/schemas'; describe('transformThresholdResultsToEcs', () => { it('should return transformed threshold results', () => { - const threshold = { - field: 'source.ip', + const threshold: Threshold = { + field: ['source.ip', 'host.name'], value: 1, + cardinality_field: 'destination.ip', + cardinality_value: 5, }; const startedAt = new Date('2020-12-17T16:27:00Z'); const transformedResults = transformThresholdResultsToEcs( { ...sampleDocSearchResultsNoSortId('abcd'), aggregations: { - threshold: { + 'threshold_0:source.ip': { buckets: [ { key: '127.0.0.1', - doc_count: 1, - top_threshold_hits: { - hits: { - hits: [sampleDocNoSortId('abcd')], - }, + doc_count: 15, + 'threshold_1:host.name': { + buckets: [ + { + key: 'garden-gnomes', + doc_count: 12, + top_threshold_hits: { + hits: { + hits: [sampleDocNoSortId('abcd')], + }, + }, + cardinality_count: { + value: 7, + }, + }, + ], }, }, ], @@ -44,7 +58,12 @@ describe('transformThresholdResultsToEcs', () => { '1234', undefined ); - const _id = calculateThresholdSignalUuid('1234', startedAt, 'source.ip', '127.0.0.1'); + const _id = calculateThresholdSignalUuid( + '1234', + startedAt, + ['source.ip', 'host.name'], + '127.0.0.1,garden-gnomes' + ); expect(transformedResults).toEqual({ took: 10, timed_out: false, @@ -67,10 +86,25 @@ describe('transformThresholdResultsToEcs', () => { _id, _index: 'test', _source: { - '@timestamp': ['2020-04-20T21:27:45+0000'], + '@timestamp': '2020-04-20T21:27:45+0000', threshold_result: { - count: 1, - value: '127.0.0.1', + terms: [ + { + field: 'source.ip', + value: '127.0.0.1', + }, + { + field: 'host.name', + value: 'garden-gnomes', + }, + ], + cardinality: [ + { + field: 'destination.ip', + value: 7, + }, + ], + count: 12, }, }, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts index dd9e1e97a2b73a..29fd189bb34f34 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts @@ -18,12 +18,13 @@ import { AlertInstanceState, AlertServices, } from '../../../../../alerts/server'; -import { RuleAlertAction } from '../../../../common/detection_engine/types'; +import { BaseHit, RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams, RefreshTypes } from '../types'; import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create'; -import { SignalSearchResponse, ThresholdAggregationBucket } from './types'; -import { calculateThresholdSignalUuid } from './utils'; +import { calculateThresholdSignalUuid, getThresholdAggregationParts } from './utils'; import { BuildRuleMessage } from './rule_messages'; +import { TermAggregationBucket } from '../../types'; +import { MultiAggBucket, SignalSearchResponse, SignalSource } from './types'; interface BulkCreateThresholdSignalsParams { actions: RuleAlertAction[]; @@ -73,52 +74,150 @@ const getTransformedHits = ( logger.warn(`No hits returned, but totalResults >= threshold.value (${threshold.value})`); return []; } + const timestampArray = get(timestampOverride ?? '@timestamp', hit.fields); + if (timestampArray == null) { + return []; + } + const timestamp = timestampArray[0]; + if (typeof timestamp !== 'string') { + return []; + } const source = { - '@timestamp': get(timestampOverride ?? '@timestamp', hit.fields), + '@timestamp': timestamp, threshold_result: { + terms: [ + { + value: ruleId, + }, + ], count: totalResults, - value: ruleId, }, }; return [ { _index: inputIndex, - _id: calculateThresholdSignalUuid(ruleId, startedAt, threshold.field), + _id: calculateThresholdSignalUuid( + ruleId, + startedAt, + Array.isArray(threshold.field) ? threshold.field : [threshold.field] + ), _source: source, }, ]; } - if (!results.aggregations?.threshold) { + const aggParts = results.aggregations && getThresholdAggregationParts(results.aggregations); + if (!aggParts) { return []; } - return results.aggregations.threshold.buckets - .map( - ({ key, doc_count: docCount, top_threshold_hits: topHits }: ThresholdAggregationBucket) => { - const hit = topHits.hits.hits[0]; - if (hit == null) { - return null; + const getCombinations = (buckets: TermAggregationBucket[], i: number, field: string) => { + return buckets.reduce((acc: MultiAggBucket[], bucket: TermAggregationBucket) => { + if (i < threshold.field.length - 1) { + const nextLevelIdx = i + 1; + const nextLevelAggParts = getThresholdAggregationParts(bucket, nextLevelIdx); + if (nextLevelAggParts == null) { + throw new Error('Something went horribly wrong'); } - - const source = { - '@timestamp': get(timestampOverride ?? '@timestamp', hit.fields), - threshold_result: { - count: docCount, - value: key, - }, + const nextLevelPath = `['${nextLevelAggParts.name}']['buckets']`; + const nextBuckets = get(nextLevelPath, bucket); + const combinations = getCombinations(nextBuckets, nextLevelIdx, nextLevelAggParts.field); + combinations.forEach((val) => { + const el = { + terms: [ + { + field, + value: bucket.key, + }, + ...val.terms, + ], + cardinality: val.cardinality, + topThresholdHits: val.topThresholdHits, + docCount: val.docCount, + }; + acc.push(el); + }); + } else { + const el = { + terms: [ + { + field, + value: bucket.key, + }, + ], + cardinality: !isEmpty(threshold.cardinality_field) + ? [ + { + field: Array.isArray(threshold.cardinality_field) + ? threshold.cardinality_field[0] + : threshold.cardinality_field!, + value: bucket.cardinality_count!.value, + }, + ] + : undefined, + topThresholdHits: bucket.top_threshold_hits, + docCount: bucket.doc_count, }; + acc.push(el); + } - return { - _index: inputIndex, - _id: calculateThresholdSignalUuid(ruleId, startedAt, threshold.field, key), - _source: source, - }; + return acc; + }, []); + }; + + return getCombinations(results.aggregations[aggParts.name].buckets, 0, aggParts.field).reduce( + (acc: Array>, bucket) => { + const hit = bucket.topThresholdHits?.hits.hits[0]; + if (hit == null) { + return acc; } - ) - .filter((bucket: ThresholdAggregationBucket) => bucket != null); + + const timestampArray = get(timestampOverride ?? '@timestamp', hit.fields); + if (timestampArray == null) { + return acc; + } + + const timestamp = timestampArray[0]; + if (typeof timestamp !== 'string') { + return acc; + } + + const source = { + '@timestamp': timestamp, + threshold_result: { + terms: bucket.terms.map((term) => { + return { + field: term.field, + value: term.value, + }; + }), + cardinality: bucket.cardinality?.map((cardinality) => { + return { + field: cardinality.field, + value: cardinality.value, + }; + }), + count: bucket.docCount, + }, + }; + + acc.push({ + _index: inputIndex, + _id: calculateThresholdSignalUuid( + ruleId, + startedAt, + Array.isArray(threshold.field) ? threshold.field : [threshold.field], + bucket.terms.map((term) => term.value).join(',') + ), + _source: source, + }); + + return acc; + }, + [] + ); }; export const transformThresholdResultsToEcs = ( @@ -149,7 +248,7 @@ export const transformThresholdResultsToEcs = ( }, }; - delete thresholdResults.aggregations; // no longer needed + delete thresholdResults.aggregations; // delete because no longer needed set(thresholdResults, 'results.hits.total', transformedHits.length); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts index 6144f1f4b3823d..7796346e9876d0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { set } from '@elastic/safer-lodash-set'; import { isEmpty } from 'lodash/fp'; import { @@ -49,38 +50,68 @@ export const findThresholdSignals = async ({ searchDuration: string; searchErrors: string[]; }> => { + const thresholdFields = Array.isArray(threshold.field) ? threshold.field : [threshold.field]; + const aggregations = threshold && !isEmpty(threshold.field) - ? { - threshold: { + ? thresholdFields.reduce((acc, field, i) => { + const aggPath = [...Array(i + 1).keys()] + .map((j) => { + return `['threshold_${j}:${thresholdFields[j]}']`; + }) + .join(`['aggs']`); + set(acc, aggPath, { terms: { - field: threshold.field, - min_doc_count: threshold.value, + field, + min_doc_count: threshold.value, // not needed on parent agg, but can help narrow down result set size: 10000, // max 10k buckets }, - aggs: { - // Get the most recent hit per bucket - top_threshold_hits: { - top_hits: { - sort: [ - { - [timestampOverride ?? '@timestamp']: { - order: 'desc', - }, + }); + if (i === threshold.field.length - 1) { + const topHitsAgg = { + top_hits: { + sort: [ + { + [timestampOverride ?? '@timestamp']: { + order: 'desc', }, - ], - fields: [ - { - field: '*', - include_unmapped: true, + }, + ], + fields: [ + { + field: '*', + include_unmapped: true, + }, + ], + size: 1, + }, + }; + // TODO: support case where threshold fields are not supplied, but cardinality is? + if (!isEmpty(threshold.cardinality_field)) { + set(acc, `${aggPath}['aggs']`, { + top_threshold_hits: topHitsAgg, + cardinality_count: { + cardinality: { + field: threshold.cardinality_field, + }, + }, + cardinality_check: { + bucket_selector: { + buckets_path: { + cardinalityCount: 'cardinality_count', }, - ], - size: 1, + script: `params.cardinalityCount >= ${threshold.cardinality_value}`, // TODO: cardinality operator + }, }, - }, - }, - }, - } + }); + } else { + set(acc, `${aggPath}['aggs']`, { + top_threshold_hits: topHitsAgg, + }); + } + } + return acc; + }, {}) : {}; return singleSearchAfter({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts index da7ee8796afbfc..710a925fe315b9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts @@ -40,7 +40,12 @@ export const signalSchema = schema.object({ severityMapping: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), threat: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), threshold: schema.maybe( - schema.object({ field: schema.nullable(schema.string()), value: schema.number() }) + schema.object({ + field: schema.nullable(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), + value: schema.number(), + cardinality_field: schema.nullable(schema.string()), // TODO: depends on `field` being defined? + cardinality_value: schema.nullable(schema.number()), + }) ), timestampOverride: schema.nullable(schema.string()), to: schema.string(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index ecb36a8b050d98..98c9dd41d179c4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -374,6 +374,10 @@ export const signalRulesAlertType = ({ } else if (isThresholdRule(type) && threshold) { const inputIndex = await getInputIndex(services, version, index); + const thresholdFields = Array.isArray(threshold.field) + ? threshold.field + : [threshold.field]; + const { filters: bucketFilters, searchErrors: previousSearchErrors, @@ -384,7 +388,7 @@ export const signalRulesAlertType = ({ services, logger, ruleId, - bucketByField: threshold.field, + bucketByFields: thresholdFields, timestampOverride, buildRuleMessage, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts index dbad1d12d2be63..8ed5929e72504a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts @@ -24,7 +24,7 @@ interface FindPreviousThresholdSignalsParams { services: AlertServices; logger: Logger; ruleId: string; - bucketByField: string; + bucketByFields: string[]; timestampOverride: TimestampOverrideOrUndefined; buildRuleMessage: BuildRuleMessage; } @@ -36,7 +36,7 @@ export const findPreviousThresholdSignals = async ({ services, logger, ruleId, - bucketByField, + bucketByFields, timestampOverride, buildRuleMessage, }: FindPreviousThresholdSignalsParams): Promise<{ @@ -44,22 +44,6 @@ export const findPreviousThresholdSignals = async ({ searchDuration: string; searchErrors: string[]; }> => { - const aggregations = { - threshold: { - terms: { - field: 'signal.threshold_result.value', - size: 10000, - }, - aggs: { - lastSignalTimestamp: { - max: { - field: 'signal.original_time', // timestamp of last event captured by bucket - }, - }, - }, - }, - }; - const filter = { bool: { must: [ @@ -68,17 +52,18 @@ export const findPreviousThresholdSignals = async ({ 'signal.rule.rule_id': ruleId, }, }, - { - term: { - 'signal.rule.threshold.field': bucketByField, - }, - }, + ...bucketByFields.map((field) => { + return { + term: { + 'signal.rule.threshold.field': field, + }, + }; + }), ], }, }; return singleSearchAfter({ - aggregations, searchAfterSortId: undefined, timestampOverride, index: indexPattern, @@ -87,7 +72,7 @@ export const findPreviousThresholdSignals = async ({ services, logger, filter, - pageSize: 0, + pageSize: 10000, // TODO: multiple pages? buildRuleMessage, excludeDocsWithTimestampOverride: false, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.test.ts new file mode 100644 index 00000000000000..ed9aa9a5ba698a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.test.ts @@ -0,0 +1,85 @@ +/* + * 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 { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; +import { mockLogger, sampleWrappedThresholdSignalHit } from './__mocks__/es_results'; +import { getThresholdBucketFilters } from './threshold_get_bucket_filters'; +import { buildRuleMessageFactory } from './rule_messages'; + +const buildRuleMessage = buildRuleMessageFactory({ + id: 'fake id', + ruleId: 'fake rule id', + index: 'fakeindex', + name: 'fake name', +}); + +describe('thresholdGetBucketFilters', () => { + let mockService: AlertServicesMock; + + beforeEach(() => { + jest.clearAllMocks(); + mockService = alertsMock.createAlertServices(); + }); + + it('should generate filters for threshold signal detection with dupe mitigation', async () => { + mockService.callCluster.mockResolvedValue({ + took: 10, + timed_out: false, + _shards: { + total: 10, + successful: 10, + failed: 0, + skipped: 0, + }, + hits: { + total: 1, + max_score: 100, + hits: [sampleWrappedThresholdSignalHit()], + }, + }); + const result = await getThresholdBucketFilters({ + from: 'now-6m', + to: 'now', + indexPattern: ['*'], + services: mockService, + logger: mockLogger, + ruleId: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', + bucketByFields: ['host.name'], + timestampOverride: undefined, + buildRuleMessage, + }); + expect(result).toEqual({ + filters: [ + { + bool: { + must_not: [ + { + bool: { + filter: [ + { + range: { + '@timestamp': { + lte: '2021-02-16T17:37:34.275Z', + }, + }, + }, + { + term: { + 'host.name': 'a hostname', + }, + }, + ], + }, + }, + ], + }, + }, + ], + searchErrors: [], + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts index 96224be181f47c..e1727c0361afc8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts @@ -5,11 +5,13 @@ * 2.0. */ +import crypto from 'crypto'; import { isEmpty } from 'lodash'; import { Filter } from 'src/plugins/data/common'; import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; import { AlertInstanceContext, @@ -17,7 +19,7 @@ import { AlertServices, } from '../../../../../alerts/server'; import { Logger } from '../../../../../../../src/core/server'; -import { ThresholdQueryBucket } from './types'; +import { ThresholdSignalHistory, ThresholdSignalHistoryRecord } from './types'; import { BuildRuleMessage } from './rule_messages'; import { findPreviousThresholdSignals } from './threshold_find_previous_signals'; @@ -28,7 +30,7 @@ interface GetThresholdBucketFiltersParams { services: AlertServices; logger: Logger; ruleId: string; - bucketByField: string; + bucketByFields: string[]; timestampOverride: TimestampOverrideOrUndefined; buildRuleMessage: BuildRuleMessage; } @@ -40,7 +42,7 @@ export const getThresholdBucketFilters = async ({ services, logger, ruleId, - bucketByField, + bucketByFields, timestampOverride, buildRuleMessage, }: GetThresholdBucketFiltersParams): Promise<{ @@ -54,20 +56,85 @@ export const getThresholdBucketFilters = async ({ services, logger, ruleId, - bucketByField, + bucketByFields, timestampOverride, buildRuleMessage, }); - const filters = searchResult.aggregations.threshold.buckets.reduce( - (acc: ESFilter[], bucket: ThresholdQueryBucket): ESFilter[] => { + const thresholdSignalHistory = searchResult.hits.hits.reduce( + (acc, hit) => { + if (!hit._source) { + return acc; + } + + const terms = bucketByFields.map((field) => { + let signalTerms = hit._source.signal?.threshold_result?.terms; + + // Handle pre-7.12 signals + if (signalTerms == null) { + signalTerms = [ + { + field: (((hit._source.rule as RulesSchema).threshold as unknown) as { field: string }) + .field, + value: ((hit._source.signal?.threshold_result as unknown) as { value: string }).value, + }, + ]; + } else if (isEmpty(signalTerms)) { + signalTerms = []; + } + + const result = signalTerms.filter((resultField) => { + return resultField.field === field; + }); + + return { + field, + value: result[0].value, + }; + }); + + const hash = crypto + .createHash('sha256') + .update( + terms + .sort((term1, term2) => (term1.field > term2.field ? 1 : -1)) + .map((field) => { + return field.value; + }) + .join(',') + ) + .digest('hex'); + + const existing = acc[hash]; + const originalTime = + hit._source.signal?.original_time != null + ? new Date(hit._source.signal?.original_time).getTime() + : undefined; + + if (existing != null) { + if (originalTime && originalTime > existing.lastSignalTimestamp) { + acc[hash].lastSignalTimestamp = originalTime; + } + } else if (originalTime) { + acc[hash] = { + terms, + lastSignalTimestamp: originalTime, + }; + } + return acc; + }, + {} + ); + + const filters = Object.values(thresholdSignalHistory).reduce( + (acc: ESFilter[], bucket: ThresholdSignalHistoryRecord): ESFilter[] => { const filter = { bool: { filter: [ { range: { [timestampOverride ?? '@timestamp']: { - lte: bucket.lastSignalTimestamp.value_as_string, + lte: new Date(bucket.lastSignalTimestamp).toISOString(), }, }, }, @@ -75,11 +142,15 @@ export const getThresholdBucketFilters = async ({ }, } as ESFilter; - if (!isEmpty(bucketByField)) { - (filter.bool.filter as ESFilter[]).push({ - term: { - [bucketByField]: bucket.key, - }, + if (!isEmpty(bucketByFields)) { + bucket.terms.forEach((term) => { + if (term.field != null) { + (filter.bool.filter as ESFilter[]).push({ + term: { + [term.field]: `${term.value}`, + }, + }); + } }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index f7ac0425b2f2e4..e5ca1f6a60456a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -17,7 +17,7 @@ import { AlertExecutorOptions, AlertServices, } from '../../../../../alerts/server'; -import { BaseSearchResponse, SearchResponse, TermAggregationBucket } from '../../types'; +import { BaseSearchResponse, SearchHit, SearchResponse, TermAggregationBucket } from '../../types'; import { EqlSearchResponse, BaseHit, @@ -50,8 +50,27 @@ export interface SignalsStatusParams { } export interface ThresholdResult { + terms?: Array<{ + field?: string; + value: string; + }>; + cardinality?: Array<{ + field: string; + value: number; + }>; count: number; - value: string; +} + +export interface ThresholdSignalHistoryRecord { + terms: Array<{ + field?: string; + value: SearchTypes; + }>; + lastSignalTimestamp: number; +} + +export interface ThresholdSignalHistory { + [hash: string]: ThresholdSignalHistoryRecord; } export interface SignalSource { @@ -74,8 +93,9 @@ export interface SignalSource { }; // signal.depth doesn't exist on pre-7.10 signals depth?: number; + original_time?: string; + threshold_result?: ThresholdResult; }; - threshold_result?: ThresholdResult; } export interface BulkItem { @@ -276,6 +296,28 @@ export interface SearchAfterAndBulkCreateReturnType { export interface ThresholdAggregationBucket extends TermAggregationBucket { top_threshold_hits: BaseSearchResponse; + cardinality_count: { + value: number; + }; +} + +export interface MultiAggBucket { + cardinality?: Array<{ + field: string; + value: number; + }>; + terms: Array<{ + field: string; + value: string; + }>; + docCount: number; + topThresholdHits?: + | { + hits: { + hits: SearchHit[]; + }; + } + | undefined; } export interface ThresholdQueryBucket extends TermAggregationBucket { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index f7e1eb7622779c..7888bb6deaab79 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -1445,13 +1445,13 @@ describe('utils', () => { describe('calculateThresholdSignalUuid', () => { it('should generate a uuid without key', () => { const startedAt = new Date('2020-12-17T16:27:00Z'); - const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, 'agent.name'); + const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, ['agent.name']); expect(signalUuid).toEqual('a4832768-a379-583a-b1a2-e2ce2ad9e6e9'); }); it('should generate a uuid with key', () => { const startedAt = new Date('2019-11-18T13:32:00Z'); - const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, 'host.ip', '1.2.3.4'); + const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, ['host.ip'], '1.2.3.4'); expect(signalUuid).toEqual('ee8870dc-45ff-5e6c-a2f9-80886651ce03'); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 323986e6ffecbc..58bf22be97bf87 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -855,7 +855,7 @@ export const createTotalHitsFromSearchResult = ({ export const calculateThresholdSignalUuid = ( ruleId: string, startedAt: Date, - thresholdField: string, + thresholdFields: string[], key?: string ): string => { // used to generate constant Threshold Signals ID when run with the same params @@ -863,7 +863,31 @@ export const calculateThresholdSignalUuid = ( const startedAtString = startedAt.toISOString(); const keyString = key ?? ''; - const baseString = `${ruleId}${startedAtString}${thresholdField}${keyString}`; + const baseString = `${ruleId}${startedAtString}${thresholdFields.join(',')}${keyString}`; return uuidv5(baseString, NAMESPACE_ID); }; + +export const getThresholdAggregationParts = ( + data: object, + index?: number +): + | { + field: string; + index: number; + name: string; + } + | undefined => { + const idx = index != null ? index.toString() : '\\d'; + const pattern = `threshold_(?${idx}):(?.*)`; + for (const key of Object.keys(data)) { + const matches = key.match(pattern); + if (matches != null && matches.groups?.name != null && matches.groups?.index != null) { + return { + field: matches.groups.name, + index: parseInt(matches.groups.index, 10), + name: key, + }; + } + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/types.ts b/x-pack/plugins/security_solution/server/lib/types.ts index 9d030e0d59bc61..a8616dc1c57d1a 100644 --- a/x-pack/plugins/security_solution/server/lib/types.ts +++ b/x-pack/plugins/security_solution/server/lib/types.ts @@ -75,8 +75,8 @@ export interface SearchHits { max_score: number; hits: Array< BaseHit & { - _type: string; - _score: number; + _type?: string; + _score?: number; _version?: number; _explanation?: Explanation; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -107,6 +107,14 @@ export type SearchHit = SearchResponse['hits']['hits'][0]; export interface TermAggregationBucket { key: string; doc_count: number; + top_threshold_hits?: { + hits: { + hits: SearchHit[]; + }; + }; + cardinality_count?: { + value: number; + }; } export interface TermAggregation { diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts index 5ed324496e609e..15d0e2d5494b8c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts @@ -24,6 +24,7 @@ export const TIMELINE_EVENTS_FIELDS = [ 'signal.rule.version', 'signal.rule.severity', 'signal.rule.risk_score', + 'signal.threshold_result', 'event.code', 'event.module', 'event.action', diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts index 5c8f7f87a6c493..10bb606dc2387e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts @@ -320,6 +320,7 @@ describe('#formatTimelineData', () => { signal: { original_time: ['2021-01-09T13:39:32.595Z'], status: ['open'], + threshold_result: ['{"count":10000,"value":"2a990c11-f61b-4c8e-b210-da2574e9f9db"}'], rule: { building_block_type: [], exceptions_list: [], From e550c19d4d2c1f00b766cca58b8bbab3da4c52d8 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Thu, 18 Feb 2021 01:09:23 -0500 Subject: [PATCH 25/93] [Security Solution][Detections] Adds ransomware exceptions (#89974) --- .../security_solution/common/ecs/index.ts | 2 + .../common/ecs/process/index.ts | 3 + .../common/ecs/ransomware/index.ts | 30 +++ .../add_exception_modal/index.test.tsx | 28 ++- .../exceptions/add_exception_modal/index.tsx | 19 +- .../exceptions/exceptionable_fields.json | 3 +- .../components/exceptions/helpers.test.tsx | 217 +++++++++++++++--- .../common/components/exceptions/helpers.tsx | 167 +++++++++++--- .../common/components/exceptions/types.ts | 31 +++ .../timeline_actions/alert_context_menu.tsx | 72 +++++- 10 files changed, 502 insertions(+), 70 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/ecs/ransomware/index.ts diff --git a/x-pack/plugins/security_solution/common/ecs/index.ts b/x-pack/plugins/security_solution/common/ecs/index.ts index ec23b677168cda..4c57f6419d5dbf 100644 --- a/x-pack/plugins/security_solution/common/ecs/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/index.ts @@ -28,6 +28,7 @@ import { UserEcs } from './user'; import { WinlogEcs } from './winlog'; import { ProcessEcs } from './process'; import { SystemEcs } from './system'; +import { Ransomware } from './ransomware'; export interface Ecs { _id: string; @@ -59,4 +60,5 @@ export interface Ecs { system?: SystemEcs; // This should be temporary eql?: { parentId: string; sequenceNumber: string }; + Ransomware?: Ransomware; } diff --git a/x-pack/plugins/security_solution/common/ecs/process/index.ts b/x-pack/plugins/security_solution/common/ecs/process/index.ts index 931adf2dd70b8b..820ecc5560e6c5 100644 --- a/x-pack/plugins/security_solution/common/ecs/process/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/process/index.ts @@ -5,7 +5,10 @@ * 2.0. */ +import { Ext } from '../file'; + export interface ProcessEcs { + Ext?: Ext; entity_id?: string[]; exit_code?: number[]; hash?: ProcessHashData; diff --git a/x-pack/plugins/security_solution/common/ecs/ransomware/index.ts b/x-pack/plugins/security_solution/common/ecs/ransomware/index.ts new file mode 100644 index 00000000000000..1724a264f8a4ca --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/ransomware/index.ts @@ -0,0 +1,30 @@ +/* + * 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 interface Ransomware { + feature?: string[]; + score?: string[]; + version?: number[]; + child_pids?: string[]; + files?: RansomwareFiles; +} + +export interface RansomwareFiles { + operation?: string[]; + entropy?: number[]; + metrics?: string[]; + extension?: string[]; + original?: OriginalRansomwareFiles; + path?: string[]; + data?: string[]; + score?: number[]; +} + +export interface OriginalRansomwareFiles { + path?: string[]; + extension?: string[]; +} diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx index e388b485ba89c1..af76a79f0e330a 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx @@ -20,7 +20,6 @@ import { stubIndexPattern } from 'src/plugins/data/common/index_patterns/index_p import { useAddOrUpdateException } from '../use_add_exception'; import { useFetchOrCreateRuleExceptionList } from '../use_fetch_or_create_rule_exception_list'; import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; -import { Ecs } from '../../../../../common/ecs'; import * as builder from '../builder'; import * as helpers from '../helpers'; import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; @@ -31,6 +30,7 @@ import { getRulesSchemaMock, } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; import { useRuleAsync } from '../../../../detections/containers/detection_engine/rules/use_rule_async'; +import { AlertData } from '../types'; jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index'); jest.mock('../../../../common/lib/kibana'); @@ -157,8 +157,11 @@ describe('When the add exception modal is opened', () => { describe('when there is alert data passed to an endpoint list exception', () => { let wrapper: ReactWrapper; beforeEach(async () => { - const alertDataMock: Ecs = { _id: 'test-id', file: { path: ['test/path'] } }; - + const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + file: { path: 'test/path' }, + }; wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> { describe('when there is alert data passed to a detection list exception', () => { let wrapper: ReactWrapper; beforeEach(async () => { - const alertDataMock: Ecs = { _id: 'test-id', file: { path: ['test/path'] } }; - + const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + file: { path: 'test/path' }, + }; wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> { describe('when there is an exception being created on a sequence eql rule type', () => { let wrapper: ReactWrapper; beforeEach(async () => { - const alertDataMock: Ecs = { _id: 'test-id', file: { path: ['test/path'] } }; (useRuleAsync as jest.Mock).mockImplementation(() => ({ rule: { ...getRulesEqlSchemaMock(), @@ -270,6 +275,11 @@ describe('When the add exception modal is opened', () => { 'sequence [process where process.name = "test.exe"] [process where process.name = "explorer.exe"]', }, })); + const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + file: { path: 'test/path' }, + }; wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> { }, }, ]); - const alertDataMock: Ecs = { _id: 'test-id', file: { path: ['test/path'] } }; + const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + file: { path: 'test/path' }, + }; wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> void; onConfirm: (didCloseAlert: boolean, didBulkCloseAlert: boolean) => void; @@ -103,6 +109,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ ruleIndices, exceptionListType, alertData, + isAlertDataLoading, onCancel, onConfirm, onRuleChange, @@ -239,7 +246,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ } else { return []; } - }, [alertData, exceptionListType, ruleExceptionList, ruleName]); + }, [exceptionListType, ruleExceptionList, ruleName, alertData]); useEffect((): void => { if (isSignalIndexPatternLoading === false && isSignalIndexLoading === false) { @@ -372,6 +379,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ (isLoadingExceptionList || isIndexPatternLoading || isSignalIndexLoading || + isAlertDataLoading || isSignalIndexPatternLoading) && ( )} @@ -382,6 +390,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ !isIndexPatternLoading && !isRuleLoading && !mlJobLoading && + !isAlertDataLoading && ruleExceptionList && ( <> @@ -421,7 +430,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ - {alertData !== undefined && alertStatus !== 'closed' && ( + {alertData != null && alertStatus !== 'closed' && ( { }); describe('getPrepopulatedItem', () => { - test('it returns prepopulated items', () => { - const prepopulatedItem = getPrepopulatedItem({ + const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + file: { path: 'some-file-path', hash: { sha256: 'some-hash' } }, + }; + test('it returns prepopulated fields with empty values', () => { + const prepopulatedItem = getPrepopulatedEndpointException({ listId: 'some_id', ruleName: 'my rule', codeSignature: { subjectName: '', trusted: '' }, - filePath: '', - sha256Hash: '', eventCode: '', + alertEcsData: { ...alertDataMock, file: { path: '', hash: { sha256: '' } } }, }); expect(prepopulatedItem.entries).toEqual([ @@ -660,14 +665,13 @@ describe('Exception helpers', () => { ]); }); - test('it returns prepopulated items with values', () => { - const prepopulatedItem = getPrepopulatedItem({ + test('it returns prepopulated items with actual values', () => { + const prepopulatedItem = getPrepopulatedEndpointException({ listId: 'some_id', ruleName: 'my rule', codeSignature: { subjectName: 'someSubjectName', trusted: 'false' }, - filePath: 'some-file-path', - sha256Hash: 'some-hash', eventCode: 'some-event-code', + alertEcsData: alertDataMock, }); expect(prepopulatedItem.entries).toEqual([ @@ -696,15 +700,15 @@ describe('Exception helpers', () => { }); }); - describe('getCodeSignatureValue', () => { + describe('getFileCodeSignature', () => { test('it works when file.Ext.code_signature is an object', () => { - const codeSignatures = getCodeSignatureValue({ + const codeSignatures = getFileCodeSignature({ _id: '123', file: { Ext: { code_signature: { - subject_name: ['some_subject'], - trusted: ['false'], + subject_name: 'some_subject', + trusted: 'false', }, }, }, @@ -714,13 +718,13 @@ describe('Exception helpers', () => { }); test('it works when file.Ext.code_signature is nested type', () => { - const codeSignatures = getCodeSignatureValue({ + const codeSignatures = getFileCodeSignature({ _id: '123', file: { Ext: { code_signature: [ - { subject_name: ['some_subject'], trusted: ['false'] }, - { subject_name: ['some_subject_2'], trusted: ['true'] }, + { subject_name: 'some_subject', trusted: 'false' }, + { subject_name: 'some_subject_2', trusted: 'true' }, ], }, }, @@ -736,11 +740,11 @@ describe('Exception helpers', () => { }); test('it returns default when file.Ext.code_signatures values are empty', () => { - const codeSignatures = getCodeSignatureValue({ + const codeSignatures = getFileCodeSignature({ _id: '123', file: { Ext: { - code_signature: { subject_name: [], trusted: [] }, + code_signature: { subject_name: '', trusted: '' }, }, }, }); @@ -749,7 +753,7 @@ describe('Exception helpers', () => { }); test('it returns default when file.Ext.code_signatures is empty array', () => { - const codeSignatures = getCodeSignatureValue({ + const codeSignatures = getFileCodeSignature({ _id: '123', file: { Ext: { @@ -762,7 +766,81 @@ describe('Exception helpers', () => { }); test('it returns default when file.Ext.code_signatures does not exist', () => { - const codeSignatures = getCodeSignatureValue({ + const codeSignatures = getFileCodeSignature({ + _id: '123', + }); + + expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]); + }); + }); + + describe('getProcessCodeSignature', () => { + test('it works when file.Ext.code_signature is an object', () => { + const codeSignatures = getProcessCodeSignature({ + _id: '123', + process: { + Ext: { + code_signature: { + subject_name: 'some_subject', + trusted: 'false', + }, + }, + }, + }); + + expect(codeSignatures).toEqual([{ subjectName: 'some_subject', trusted: 'false' }]); + }); + + test('it works when file.Ext.code_signature is nested type', () => { + const codeSignatures = getProcessCodeSignature({ + _id: '123', + process: { + Ext: { + code_signature: [ + { subject_name: 'some_subject', trusted: 'false' }, + { subject_name: 'some_subject_2', trusted: 'true' }, + ], + }, + }, + }); + + expect(codeSignatures).toEqual([ + { subjectName: 'some_subject', trusted: 'false' }, + { + subjectName: 'some_subject_2', + trusted: 'true', + }, + ]); + }); + + test('it returns default when file.Ext.code_signatures values are empty', () => { + const codeSignatures = getProcessCodeSignature({ + _id: '123', + process: { + Ext: { + code_signature: { subject_name: '', trusted: '' }, + }, + }, + }); + + expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]); + }); + + test('it returns default when file.Ext.code_signatures is empty array', () => { + const codeSignatures = getProcessCodeSignature({ + _id: '123', + process: { + Ext: { + code_signature: [], + }, + }, + }); + + expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]); + }); + + test('it returns default when file.Ext.code_signatures does not exist', () => { + const codeSignatures = getProcessCodeSignature({ _id: '123', }); @@ -771,23 +849,23 @@ describe('Exception helpers', () => { }); describe('defaultEndpointExceptionItems', () => { - test('it should return pre-populated items', () => { + test('it should return pre-populated Endpoint items for non-specified event code', () => { const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', { _id: '123', file: { Ext: { code_signature: [ - { subject_name: ['some_subject'], trusted: ['false'] }, - { subject_name: ['some_subject_2'], trusted: ['true'] }, + { subject_name: 'some_subject', trusted: 'false' }, + { subject_name: 'some_subject_2', trusted: 'true' }, ], }, - path: ['some file path'], + path: 'some file path', hash: { - sha256: ['some hash'], + sha256: 'some hash', }, }, event: { - code: ['some event code'], + code: 'some event code', }, }); @@ -838,5 +916,88 @@ describe('Exception helpers', () => { { field: 'event.code', operator: 'included', type: 'match', value: 'some event code' }, ]); }); + + test('it should return pre-populated ransomware items for event code `ransomware`', () => { + const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', { + _id: '123', + process: { + Ext: { + code_signature: [ + { subject_name: 'some_subject', trusted: 'false' }, + { subject_name: 'some_subject_2', trusted: 'true' }, + ], + }, + executable: 'some file path', + hash: { + sha256: 'some hash', + }, + }, + Ransomware: { + feature: 'some ransomware feature', + }, + event: { + code: 'ransomware', + }, + }); + + expect(defaultItems[0].entries).toEqual([ + { + entries: [ + { + field: 'subject_name', + operator: 'included', + type: 'match', + value: 'some_subject', + }, + { field: 'trusted', operator: 'included', type: 'match', value: 'false' }, + ], + field: 'process.Ext.code_signature', + type: 'nested', + }, + { + field: 'process.executable', + operator: 'included', + type: 'match', + value: 'some file path', + }, + { field: 'process.hash.sha256', operator: 'included', type: 'match', value: 'some hash' }, + { + field: 'Ransomware.feature', + operator: 'included', + type: 'match', + value: 'some ransomware feature', + }, + { field: 'event.code', operator: 'included', type: 'match', value: 'ransomware' }, + ]); + expect(defaultItems[1].entries).toEqual([ + { + entries: [ + { + field: 'subject_name', + operator: 'included', + type: 'match', + value: 'some_subject_2', + }, + { field: 'trusted', operator: 'included', type: 'match', value: 'true' }, + ], + field: 'process.Ext.code_signature', + type: 'nested', + }, + { + field: 'process.executable', + operator: 'included', + type: 'match', + value: 'some file path', + }, + { field: 'process.hash.sha256', operator: 'included', type: 'match', value: 'some hash' }, + { + field: 'Ransomware.feature', + operator: 'included', + type: 'match', + value: 'some ransomware feature', + }, + { field: 'event.code', operator: 'included', type: 'match', value: 'ransomware' }, + ]); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index 1a66dd2f27cc2b..507fd51a90486b 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -16,6 +16,7 @@ import { BuilderEntry, CreateExceptionListItemBuilderSchema, ExceptionsBuilderExceptionItem, + Flattened, } from './types'; import { EXCEPTION_OPERATORS, isOperator } from '../autocomplete/operators'; import { OperatorOption } from '../autocomplete/types'; @@ -264,6 +265,16 @@ export const enrichNewExceptionItemsWithComments = ( }); }; +export const buildGetAlertByIdQuery = (id: string | undefined) => ({ + query: { + match: { + _id: { + query: id || '', + }, + }, + }, +}); + /** * Adds new and existing comments to exceptionItem * @param exceptionItem existing ExceptionItem @@ -358,31 +369,50 @@ export const entryHasListType = ( * Returns the value for `file.Ext.code_signature` which * can be an object or array of objects */ -export const getCodeSignatureValue = ( - alertData: Ecs +export const getFileCodeSignature = ( + alertData: Flattened ): Array<{ subjectName: string; trusted: string }> => { const { file } = alertData; const codeSignature = file && file.Ext && file.Ext.code_signature; - // Pre 7.10 file.Ext.code_signature was mistakenly populated as - // a single object with subject_name and trusted. + return getCodeSignatureValue(codeSignature); +}; + +/** + * Returns the value for `process.Ext.code_signature` which + * can be an object or array of objects + */ +export const getProcessCodeSignature = ( + alertData: Flattened +): Array<{ subjectName: string; trusted: string }> => { + const { process } = alertData; + const codeSignature = process && process.Ext && process.Ext.code_signature; + return getCodeSignatureValue(codeSignature); +}; + +/** + * Pre 7.10 `Ext.code_signature` fields were mistakenly populated as + * a single object with subject_name and trusted. + */ +export const getCodeSignatureValue = ( + codeSignature: Flattened | Flattened | undefined +): Array<{ subjectName: string; trusted: string }> => { if (Array.isArray(codeSignature) && codeSignature.length > 0) { - return codeSignature.map((signature) => ({ - subjectName: (signature.subject_name && signature.subject_name[0]) ?? '', - trusted: (signature.trusted && signature.trusted[0]) ?? '', - })); + return codeSignature.map((signature) => { + return { + subjectName: signature.subject_name ?? '', + trusted: signature.trusted ?? '', + }; + }); } else { - const signature: CodeSignature | undefined = !Array.isArray(codeSignature) + const signature: Flattened | undefined = !Array.isArray(codeSignature) ? codeSignature : undefined; - const subjectName: string | undefined = - signature && signature.subject_name && signature.subject_name[0]; - const trusted: string | undefined = signature && signature.trusted && signature.trusted[0]; return [ { - subjectName: subjectName ?? '', - trusted: trusted ?? '', + subjectName: signature?.subject_name ?? '', + trusted: signature?.trusted ?? '', }, ]; } @@ -391,23 +421,24 @@ export const getCodeSignatureValue = ( /** * Returns the default values from the alert data to autofill new endpoint exceptions */ -export const getPrepopulatedItem = ({ +export const getPrepopulatedEndpointException = ({ listId, ruleName, codeSignature, - filePath, - sha256Hash, eventCode, listNamespace = 'agnostic', + alertEcsData, }: { listId: string; listNamespace?: NamespaceType; ruleName: string; codeSignature: { subjectName: string; trusted: string }; - filePath: string; - sha256Hash: string; eventCode: string; + alertEcsData: Flattened; }): ExceptionsBuilderExceptionItem => { + const { file } = alertEcsData; + const filePath = file?.path ?? ''; + const sha256Hash = file?.hash?.sha256 ?? ''; return { ...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }), entries: [ @@ -451,6 +482,77 @@ export const getPrepopulatedItem = ({ }; }; +/** + * Returns the default values from the alert data to autofill new endpoint exceptions + */ +export const getPrepopulatedRansomwareException = ({ + listId, + ruleName, + codeSignature, + eventCode, + listNamespace = 'agnostic', + alertEcsData, +}: { + listId: string; + listNamespace?: NamespaceType; + ruleName: string; + codeSignature: { subjectName: string; trusted: string }; + eventCode: string; + alertEcsData: Flattened; +}): ExceptionsBuilderExceptionItem => { + const { process, Ransomware } = alertEcsData; + const sha256Hash = process?.hash?.sha256 ?? ''; + const executable = process?.executable ?? ''; + const ransomwareFeature = Ransomware?.feature ?? ''; + return { + ...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }), + entries: [ + { + field: 'process.Ext.code_signature', + type: 'nested', + entries: [ + { + field: 'subject_name', + operator: 'included', + type: 'match', + value: codeSignature != null ? codeSignature.subjectName : '', + }, + { + field: 'trusted', + operator: 'included', + type: 'match', + value: codeSignature != null ? codeSignature.trusted : '', + }, + ], + }, + { + field: 'process.executable', + operator: 'included', + type: 'match', + value: executable ?? '', + }, + { + field: 'process.hash.sha256', + operator: 'included', + type: 'match', + value: sha256Hash ?? '', + }, + { + field: 'Ransomware.feature', + operator: 'included', + type: 'match', + value: ransomwareFeature ?? '', + }, + { + field: 'event.code', + operator: 'included', + type: 'match', + value: eventCode ?? '', + }, + ], + }; +}; + /** * Determines whether or not any entries within the given exceptionItems contain values not in the specified ECS mapping */ @@ -487,18 +589,31 @@ export const entryHasNonEcsType = ( export const defaultEndpointExceptionItems = ( listId: string, ruleName: string, - alertEcsData: Ecs + alertEcsData: Flattened ): ExceptionsBuilderExceptionItem[] => { - const { file, event: alertEvent } = alertEcsData; + const { event: alertEvent } = alertEcsData; + const eventCode = alertEvent?.code ?? ''; + + if (eventCode === 'ransomware') { + return getProcessCodeSignature(alertEcsData).map((codeSignature) => + getPrepopulatedRansomwareException({ + listId, + ruleName, + eventCode, + codeSignature, + alertEcsData, + }) + ); + } - return getCodeSignatureValue(alertEcsData).map((codeSignature) => - getPrepopulatedItem({ + // By default return the standard prepopulated Endpoint Exception fields + return getFileCodeSignature(alertEcsData).map((codeSignature) => + getPrepopulatedEndpointException({ listId, ruleName, - filePath: file && file.path ? file.path[0] : '', - sha256Hash: file && file.hash && file.hash.sha256 ? file.hash.sha256[0] : '', - eventCode: alertEvent && alertEvent.code ? alertEvent.code[0] : '', + eventCode, codeSignature, + alertEcsData, }) ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts b/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts index 3593fe3d054910..6108a21ce5624a 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts @@ -6,6 +6,8 @@ */ import { ReactNode } from 'react'; +import { Ecs } from '../../../../common/ecs'; +import { CodeSignature } from '../../../../common/ecs/file'; import { IFieldType } from '../../../../../../../src/plugins/data/common'; import { OperatorOption } from '../autocomplete/types'; import { @@ -104,3 +106,32 @@ export type CreateExceptionListItemBuilderSchema = Omit< export type ExceptionsBuilderExceptionItem = | ExceptionListItemBuilderSchema | CreateExceptionListItemBuilderSchema; + +export interface FlattenedCodeSignature { + subject_name: string; + trusted: string; +} + +export type Flattened = { + [K in keyof T]: T[K] extends infer AliasType + ? AliasType extends CodeSignature[] + ? FlattenedCodeSignature[] + : AliasType extends Array + ? rawType + : AliasType extends object + ? Flattened + : AliasType + : never; +}; + +export type AlertData = { + '@timestamp': string; +} & Flattened; + +export interface EcsHit { + _id: string; + _index: string; + _source: { + '@timestamp': string; + } & Omit, '_id' | '_index'>; +} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 9236700c2b2e24..b2e5638ff120ee 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -18,6 +18,7 @@ import { import styled from 'styled-components'; import { getOr } from 'lodash/fp'; +import { buildGetAlertByIdQuery } from '../../../../common/components/exceptions/helpers'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { TimelineId } from '../../../../../common/types/timeline'; import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; @@ -29,7 +30,10 @@ import { FILTER_OPEN, FILTER_CLOSED, FILTER_IN_PROGRESS } from '../alerts_filter import { updateAlertStatusAction } from '../actions'; import { SetEventsDeletedProps, SetEventsLoadingProps } from '../types'; import { Ecs } from '../../../../../common/ecs'; -import { AddExceptionModal } from '../../../../common/components/exceptions/add_exception_modal'; +import { + AddExceptionModal, + AddExceptionModalProps, +} from '../../../../common/components/exceptions/add_exception_modal'; import * as i18nCommon from '../../../../common/translations'; import * as i18n from '../translations'; import { @@ -40,6 +44,9 @@ import { import { inputsModel } from '../../../../common/store'; import { useUserData } from '../../user_info'; import { ExceptionListType } from '../../../../../common/shared_imports'; +import { AlertData, EcsHit } from '../../../../common/components/exceptions/types'; +import { useQueryAlerts } from '../../../containers/detection_engine/alerts/use_query'; +import { useSignalIndex } from '../../../containers/detection_engine/alerts/use_signal_index'; interface AlertContextMenuProps { ariaLabel?: string; @@ -386,12 +393,12 @@ const AlertContextMenuComponent: React.FC = ({ {exceptionModalType != null && ruleId != null && ecsRowData != null && ( - & { + ecsData: Ecs; +}; + +/** + * This component exists to fetch needed data outside of the AddExceptionModal + * Due to the conditional nature of the modal and how we use the `ecsData` field, + * we cannot use the fetch hook within the modal component itself + */ +const AddExceptionModalWrapper: React.FC = ({ + ruleName, + ruleId, + ruleIndices, + exceptionListType, + ecsData, + onCancel, + onConfirm, + alertStatus, + onRuleChange, +}) => { + const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); + + const { loading: isLoadingAlertData, data } = useQueryAlerts( + buildGetAlertByIdQuery(ecsData?._id), + signalIndexName + ); + + const enrichedAlert: AlertData | undefined = useMemo(() => { + if (isLoadingAlertData === false) { + const hit = data?.hits.hits[0]; + if (!hit) { + return undefined; + } + const { _id, _index, _source } = hit; + return { ..._source, _id, _index }; + } + }, [data?.hits.hits, isLoadingAlertData]); + + const isLoading = isLoadingAlertData && isSignalIndexLoading; + + return ( + + ); +}; From f022792f6a9301012d3eb36e2f8c6918bcdb94ec Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Thu, 18 Feb 2021 00:15:03 -0600 Subject: [PATCH 26/93] Latency percentile labels and instances table support (#91758) * Add "(avg.)" to dependencies table column label. (This one is always average.) * Add latency aggregation type support to the instances table. * Make the memory usage column a bit wider (it was cut off.) --- .../get_latency_column_label.ts | 38 +++++++++++++++++++ .../index.tsx | 2 +- ...ice_overview_instances_chart_and_table.tsx | 15 ++++++-- .../index.tsx | 14 +++---- .../get_columns.tsx | 28 ++------------ .../get_service_instance_transaction_stats.ts | 23 +++++++---- .../services/get_service_instances/index.ts | 2 + x-pack/plugins/apm/server/routes/services.ts | 13 ++++++- .../translations/translations/ja-JP.json | 4 -- .../translations/translations/zh-CN.json | 4 -- .../tests/service_overview/instances.ts | 3 ++ 11 files changed, 94 insertions(+), 52 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/service_overview/get_latency_column_label.ts diff --git a/x-pack/plugins/apm/public/components/app/service_overview/get_latency_column_label.ts b/x-pack/plugins/apm/public/components/app/service_overview/get_latency_column_label.ts new file mode 100644 index 00000000000000..fda45db98d0cae --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/get_latency_column_label.ts @@ -0,0 +1,38 @@ +/* + * 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 { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; + +export function getLatencyColumnLabel( + latencyAggregationType?: LatencyAggregationType +) { + switch (latencyAggregationType) { + case LatencyAggregationType.avg: + return i18n.translate('xpack.apm.serviceOverview.latencyColumnAvgLabel', { + defaultMessage: 'Latency (avg.)', + }); + + case LatencyAggregationType.p95: + return i18n.translate('xpack.apm.serviceOverview.latencyColumnP95Label', { + defaultMessage: 'Latency (95th)', + }); + + case LatencyAggregationType.p99: + return i18n.translate('xpack.apm.serviceOverview.latencyColumnP99Label', { + defaultMessage: 'Latency (99th)', + }); + + default: + return i18n.translate( + 'xpack.apm.serviceOverview.latencyColumnDefaultLabel', + { + defaultMessage: 'Latency', + } + ); + } +} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index 2f37e8e4238d8b..a4647bc148b1e2 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -93,7 +93,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { name: i18n.translate( 'xpack.apm.serviceOverview.dependenciesTableColumnLatency', { - defaultMessage: 'Latency', + defaultMessage: 'Latency (avg.)', } ), width: px(unit * 10), diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx index 819d65a5d9415e..2f2aaf3156b93a 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx @@ -25,13 +25,13 @@ export function ServiceOverviewInstancesChartAndTable({ const { transactionType } = useApmServiceContext(); const { - urlParams: { environment, start, end }, + urlParams: { environment, latencyAggregationType, start, end }, uiFilters, } = useUrlParams(); const { data = [], status } = useFetcher( (callApmApi) => { - if (!start || !end || !transactionType) { + if (!start || !end || !transactionType || !latencyAggregationType) { return; } @@ -44,6 +44,7 @@ export function ServiceOverviewInstancesChartAndTable({ }, query: { environment, + latencyAggregationType, start, end, transactionType, @@ -53,7 +54,15 @@ export function ServiceOverviewInstancesChartAndTable({ }, }); }, - [environment, start, end, serviceName, transactionType, uiFilters] + [ + environment, + latencyAggregationType, + start, + end, + serviceName, + transactionType, + uiFilters, + ] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx index 62ae4b7bc34462..83ad506e8659b6 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx @@ -24,6 +24,7 @@ import { asTransactionRate, } from '../../../../../common/utils/formatters'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { px, unit } from '../../../../style/variables'; @@ -32,6 +33,7 @@ import { MetricOverviewLink } from '../../../shared/Links/apm/MetricOverviewLink import { ServiceNodeMetricOverviewLink } from '../../../shared/Links/apm/ServiceNodeMetricOverviewLink'; import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; +import { getLatencyColumnLabel } from '../get_latency_column_label'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; type ServiceInstanceItem = ValuesType< @@ -50,6 +52,9 @@ export function ServiceOverviewInstancesTable({ status, }: Props) { const { agentName } = useApmServiceContext(); + const { + urlParams: { latencyAggregationType }, + } = useUrlParams(); const columns: Array> = [ { @@ -95,12 +100,7 @@ export function ServiceOverviewInstancesTable({ }, { field: 'latencyValue', - name: i18n.translate( - 'xpack.apm.serviceOverview.instancesTableColumnLatency', - { - defaultMessage: 'Latency', - } - ), + name: getLatencyColumnLabel(latencyAggregationType), width: px(unit * 10), render: (_, { latency }) => { return ( @@ -182,7 +182,7 @@ export function ServiceOverviewInstancesTable({ defaultMessage: 'Memory usage (avg.)', } ), - width: px(unit * 8), + width: px(unit * 9), render: (_, { memoryUsage }) => { return ( ; @@ -28,35 +30,13 @@ type ServiceTransactionGroupItem = ValuesType< >; type TransactionGroupComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics'>; -function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { - switch (latencyAggregationType) { - case 'avg': - return i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnLatency.avg', - { defaultMessage: 'Latency (avg.)' } - ); - - case 'p95': - return i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnLatency.p95', - { defaultMessage: 'Latency (95th)' } - ); - - case 'p99': - return i18n.translate( - 'xpack.apm.serviceOverview.transactionsTableColumnLatency.p99', - { defaultMessage: 'Latency (99th)' } - ); - } -} - export function getColumns({ serviceName, latencyAggregationType, transactionGroupComparisonStatistics, }: { serviceName: string; - latencyAggregationType?: string; + latencyAggregationType?: LatencyAggregationType; transactionGroupComparisonStatistics?: TransactionGroupComparisonStatistics; }): Array> { return [ @@ -88,7 +68,7 @@ export function getColumns({ { field: 'latency', sortable: true, - name: getLatencyAggregationTypeLabel(latencyAggregationType), + name: getLatencyColumnLabel(latencyAggregationType), width: px(unit * 10), render: (_, { latency, name }) => { const timeseries = diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts index b56625bcebc998..620fd9828bd37f 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts @@ -22,9 +22,14 @@ import { } from '../../helpers/aggregated_transactions'; import { calculateThroughput } from '../../helpers/calculate_throughput'; import { withApmSpan } from '../../../utils/with_apm_span'; +import { + getLatencyAggregation, + getLatencyValue, +} from '../../helpers/latency_aggregation_type'; export async function getServiceInstanceTransactionStats({ environment, + latencyAggregationType, setup, transactionType, serviceName, @@ -46,11 +51,7 @@ export async function getServiceInstanceTransactionStats({ ); const subAggs = { - avg_transaction_duration: { - avg: { - field, - }, - }, + ...getLatencyAggregation(latencyAggregationType, field), failures: { filter: { term: { @@ -117,7 +118,7 @@ export async function getServiceInstanceTransactionStats({ (serviceNodeBucket) => { const { doc_count: count, - avg_transaction_duration: avgTransactionDuration, + latency, key, failures, timeseries, @@ -140,10 +141,16 @@ export async function getServiceInstanceTransactionStats({ })), }, latency: { - value: avgTransactionDuration.value, + value: getLatencyValue({ + aggregation: latency, + latencyAggregationType, + }), timeseries: timeseries.buckets.map((dateBucket) => ({ x: dateBucket.key, - y: dateBucket.avg_transaction_duration.value, + y: getLatencyValue({ + aggregation: dateBucket.latency, + latencyAggregationType, + }), })), }, }; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts index 4c16940e6d2538..7c0124f4ce0040 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { joinByKey } from '../../../../common/utils/join_by_key'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; @@ -13,6 +14,7 @@ import { getServiceInstanceTransactionStats } from './get_service_instance_trans export interface ServiceInstanceParams { environment?: string; + latencyAggregationType: LatencyAggregationType; setup: Setup & SetupTimeRange; serviceName: string; transactionType: string; diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index e59b438305b349..24c7c6e3e23d7e 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -32,6 +32,10 @@ import { uiFiltersRt, } from './default_api_types'; import { withApmSpan } from '../utils/with_apm_span'; +import { + latencyAggregationTypeRt, + LatencyAggregationType, +} from '../../common/latency_aggregation_types'; export const servicesRoute = createRoute({ endpoint: 'GET /api/apm/services', @@ -401,7 +405,11 @@ export const serviceInstancesRoute = createRoute({ serviceName: t.string, }), query: t.intersection([ - t.type({ transactionType: t.string, numBuckets: toNumberRt }), + t.type({ + latencyAggregationType: latencyAggregationTypeRt, + transactionType: t.string, + numBuckets: toNumberRt, + }), environmentRt, uiFiltersRt, rangeRt, @@ -412,6 +420,8 @@ export const serviceInstancesRoute = createRoute({ const setup = await setupRequest(context, request); const { serviceName } = context.params.path; const { environment, transactionType, numBuckets } = context.params.query; + const latencyAggregationType = (context.params.query + .latencyAggregationType as unknown) as LatencyAggregationType; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup @@ -419,6 +429,7 @@ export const serviceInstancesRoute = createRoute({ return getServiceInstances({ environment, + latencyAggregationType, serviceName, setup, transactionType, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a5d4b5d991b4a8..c561339d1a6670 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5244,7 +5244,6 @@ "xpack.apm.serviceOverview.errorsTableTitle": "エラー", "xpack.apm.serviceOverview.instancesTableColumnCpuUsage": "CPU使用状況(平均)", "xpack.apm.serviceOverview.instancesTableColumnErrorRate": "エラー率", - "xpack.apm.serviceOverview.instancesTableColumnLatency": "レイテンシ", "xpack.apm.serviceOverview.instancesTableColumnMemoryUsage": "メモリー使用状況(平均)", "xpack.apm.serviceOverview.instancesTableColumnNodeName": "ノード名", "xpack.apm.serviceOverview.instancesTableColumnThroughput": "トラフィック", @@ -5257,9 +5256,6 @@ "xpack.apm.serviceOverview.throughtputChartTitle": "トラフィック", "xpack.apm.serviceOverview.transactionsTableColumnErrorRate": "エラー率", "xpack.apm.serviceOverview.transactionsTableColumnImpact": "インパクト", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.avg": "レイテンシ(平均)", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.p95": "レイテンシ(95 番目)", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.p99": "レイテンシ(99 番目)", "xpack.apm.serviceOverview.transactionsTableColumnName": "名前", "xpack.apm.serviceOverview.transactionsTableLinkText": "トランザクションを表示", "xpack.apm.serviceOverview.transactionsTableTitle": "トランザクション", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f84b993fe56336..ab09f6c1ec56e8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5253,7 +5253,6 @@ "xpack.apm.serviceOverview.errorsTableTitle": "错误", "xpack.apm.serviceOverview.instancesTableColumnCpuUsage": "CPU 使用率(平均值)", "xpack.apm.serviceOverview.instancesTableColumnErrorRate": "错误率", - "xpack.apm.serviceOverview.instancesTableColumnLatency": "延迟", "xpack.apm.serviceOverview.instancesTableColumnMemoryUsage": "内存使用率(平均值)", "xpack.apm.serviceOverview.instancesTableColumnNodeName": "节点名称", "xpack.apm.serviceOverview.instancesTableColumnThroughput": "流量", @@ -5266,9 +5265,6 @@ "xpack.apm.serviceOverview.throughtputChartTitle": "流量", "xpack.apm.serviceOverview.transactionsTableColumnErrorRate": "错误率", "xpack.apm.serviceOverview.transactionsTableColumnImpact": "影响", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.avg": "延迟(平均值)", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.p95": "延迟(第 95 个)", - "xpack.apm.serviceOverview.transactionsTableColumnLatency.p99": "延迟(第 99 个)", "xpack.apm.serviceOverview.transactionsTableColumnName": "名称", "xpack.apm.serviceOverview.transactionsTableLinkText": "查看事务", "xpack.apm.serviceOverview.transactionsTableTitle": "事务", diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts index 7c1b01d2715b66..cca40a6950007f 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts @@ -35,6 +35,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { url.format({ pathname: `/api/apm/services/opbeans-java/service_overview_instances`, query: { + latencyAggregationType: 'avg', start, end, numBuckets: 20, @@ -63,6 +64,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { url.format({ pathname: `/api/apm/services/opbeans-java/service_overview_instances`, query: { + latencyAggregationType: 'avg', start, end, numBuckets: 20, @@ -146,6 +148,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { url.format({ pathname: `/api/apm/services/opbeans-ruby/service_overview_instances`, query: { + latencyAggregationType: 'avg', start, end, numBuckets: 20, From 04723679bd3211d11c1912a0dd4fea3bc682a7bb Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Thu, 18 Feb 2021 00:13:42 -0700 Subject: [PATCH 27/93] [Security Solutions] Improves query performance of first and last events (#91790) ## Summary Fixes performance issues and timeouts with first and last events. Previously we were using aggregations and max/min but that is slower in orders of magnitude vs. using sorting + size: 1 + track_total_hits: false. This PR also splits out the aggregate of first/last into two separate calls as we were calling the same aggregate twice for first/last twice rather than calling one query for first even and a second query for last event which is also faster when you don't use an aggregate even if you ran them multiple times back to back compared to using aggregates for min/max's. For how we determined performance numbers quantifiably and how we did profiling see this comment: https://github.com/elastic/kibana/issues/91269#issuecomment-779645203 Shoutout to @dplumlee for the help and the first cut and draft of this PR that I morphed into this PR. For any manual testing, please test: * Hosts overview page and look at "last events" * Network overview page and look at "last events" * "last events" on the timeline * Host details page the first and last seen event for a particular host. The more records the better. * Network details page and the first and last seen for a particular network. 127.0.0.1 is good as that has a lot of records. For qualitative viewing you should see a noticeable difference like so where the first seen/last seen/last events show up quicker on detailed pages from the left side compared to the right side: ![quantify_perf](https://user-images.githubusercontent.com/1151048/108309335-99773600-716e-11eb-8dae-c289947b188c.gif) ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../security_solution/hosts/common/index.ts | 2 - .../hosts/first_last_seen/index.ts | 2 + .../security_solution/hosts/index.ts | 2 +- .../security_solution/index.ts | 6 +- .../components/first_last_seen_host/index.tsx | 1 + .../hosts/first_last_seen/index.tsx | 9 +- .../factory/hosts/index.test.ts | 4 +- .../security_solution/factory/hosts/index.ts | 4 +- .../hosts/last_first_seen/__mocks__/index.ts | 180 +++++++++++++++--- .../hosts/last_first_seen/index.test.ts | 66 +++++-- .../factory/hosts/last_first_seen/index.ts | 44 +++-- ...query.first_or_last_seen_host.dsl.test.ts} | 2 +- ...s => query.first_or_last_seen_host.dsl.ts} | 20 +- .../factory/events/last_event_time/index.ts | 12 +- .../query.events_last_event_time.dsl.ts | 41 ++-- .../apis/security_solution/hosts.ts | 56 +++++- 16 files changed, 352 insertions(+), 99 deletions(-) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/{query.last_first_seen_host.dsl.test.ts => query.first_or_last_seen_host.dsl.test.ts} (82%) rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/{query.last_first_seen_host.dsl.ts => query.first_or_last_seen_host.dsl.ts} (69%) diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts index 7e19944ea5856c..11dc8ee2f6a825 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts @@ -70,7 +70,6 @@ export interface HostAggEsItem { cloud_machine_type?: HostBuckets; cloud_provider?: HostBuckets; cloud_region?: HostBuckets; - firstSeen?: HostValue; host_architecture?: HostBuckets; host_id?: HostBuckets; host_ip?: HostBuckets; @@ -80,7 +79,6 @@ export interface HostAggEsItem { host_os_version?: HostBuckets; host_type?: HostBuckets; key?: string; - lastSeen?: HostValue; os?: HostOsHitsItem; } diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts index a7e074058daa32..b3e7b14aed000a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts @@ -13,7 +13,9 @@ import { HostsFields } from '../common'; export interface HostFirstLastSeenRequestOptions extends Partial> { hostName: string; + order: 'asc' | 'desc'; } + export interface HostFirstLastSeenStrategyResponse extends IEsSearchResponse { inspect?: Maybe; firstSeen?: Maybe; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts index a430c429d54dca..fa3029405dc225 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts @@ -17,7 +17,7 @@ export * from './uncommon_processes'; export enum HostsQueries { authentications = 'authentications', details = 'details', - firstLastSeen = 'firstLastSeen', + firstOrLastSeen = 'firstOrLastSeen', hosts = 'hosts', overview = 'overviewHost', uncommonProcesses = 'uncommonProcesses', diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index de88409549f8a8..319933be5c79e5 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -15,7 +15,6 @@ import { HostAuthenticationsStrategyResponse, HostOverviewRequestOptions, HostFirstLastSeenStrategyResponse, - HostFirstLastSeenRequestOptions, HostsQueries, HostsRequestOptions, HostsStrategyResponse, @@ -28,6 +27,7 @@ import { HostsKpiHostsRequestOptions, HostsKpiUniqueIpsStrategyResponse, HostsKpiUniqueIpsRequestOptions, + HostFirstLastSeenRequestOptions, } from './hosts'; import { NetworkQueries, @@ -111,7 +111,7 @@ export type StrategyResponseType = T extends HostsQ ? HostsOverviewStrategyResponse : T extends HostsQueries.authentications ? HostAuthenticationsStrategyResponse - : T extends HostsQueries.firstLastSeen + : T extends HostsQueries.firstOrLastSeen ? HostFirstLastSeenStrategyResponse : T extends HostsQueries.uncommonProcesses ? HostsUncommonProcessesStrategyResponse @@ -159,7 +159,7 @@ export type StrategyRequestType = T extends HostsQu ? HostOverviewRequestOptions : T extends HostsQueries.authentications ? HostAuthenticationsRequestOptions - : T extends HostsQueries.firstLastSeen + : T extends HostsQueries.firstOrLastSeen ? HostFirstLastSeenRequestOptions : T extends HostsQueries.uncommonProcesses ? HostsUncommonProcessesRequestOptions diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx index b69614e73e7f60..540191ac63b6ca 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx @@ -31,6 +31,7 @@ export const FirstLastSeenHost = React.memo( docValueFields, hostName, indexNames, + order: type === FirstLastSeenHostType.FIRST_SEEN ? 'asc' : 'desc', }); const valueSeen = useMemo( () => (type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen), diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx index 1394e2660a5aea..da574dfa4ea441 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx @@ -11,8 +11,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useKibana } from '../../../../common/lib/kibana'; import { HostsQueries, - HostFirstLastSeenRequestOptions, HostFirstLastSeenStrategyResponse, + HostFirstLastSeenRequestOptions, } from '../../../../../common/search_strategy/security_solution'; import * as i18n from './translations'; @@ -30,17 +30,20 @@ export interface FirstLastSeenHostArgs { errorMessage: string | null; firstSeen?: string | null; lastSeen?: string | null; + order: 'asc' | 'desc' | null; } interface UseHostFirstLastSeen { docValueFields: DocValueFields[]; hostName: string; indexNames: string[]; + order: 'asc' | 'desc'; } export const useFirstLastSeenHost = ({ docValueFields, hostName, indexNames, + order, }: UseHostFirstLastSeen): [boolean, FirstLastSeenHostArgs] => { const { data, notifications } = useKibana().services; const abortCtrl = useRef(new AbortController()); @@ -51,12 +54,14 @@ export const useFirstLastSeenHost = ({ ] = useState({ defaultIndex: indexNames, docValueFields: docValueFields ?? [], - factoryQueryType: HostsQueries.firstLastSeen, + factoryQueryType: HostsQueries.firstOrLastSeen, hostName, + order, }); const [firstLastSeenHostResponse, setFirstLastSeenHostResponse] = useState( { + order: null, firstSeen: null, lastSeen: null, errorMessage: null, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts index 2a669fcd9f2e93..5575b4fb487e7f 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts @@ -10,7 +10,7 @@ import { HostsQueries, HostsKpiQueries } from '../../../../../common/search_stra import { allHosts } from './all'; import { hostDetails } from './details'; import { hostOverview } from './overview'; -import { firstLastSeenHost } from './last_first_seen'; +import { firstOrLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; import { authentications } from './authentications'; import { hostsKpiAuthentications } from './kpi/authentications'; @@ -33,7 +33,7 @@ describe('hostsFactory', () => { [HostsQueries.details]: hostDetails, [HostsQueries.hosts]: allHosts, [HostsQueries.overview]: hostOverview, - [HostsQueries.firstLastSeen]: firstLastSeenHost, + [HostsQueries.firstOrLastSeen]: firstOrLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsQueries.authentications]: authentications, [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts index 9388807f25a6fb..5cee547a6b365f 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -15,7 +15,7 @@ import { SecuritySolutionFactory } from '../types'; import { allHosts } from './all'; import { hostDetails } from './details'; import { hostOverview } from './overview'; -import { firstLastSeenHost } from './last_first_seen'; +import { firstOrLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; import { authentications } from './authentications'; import { hostsKpiAuthentications } from './kpi/authentications'; @@ -29,7 +29,7 @@ export const hostsFactory: Record< [HostsQueries.details]: hostDetails, [HostsQueries.hosts]: allHosts, [HostsQueries.overview]: hostOverview, - [HostsQueries.firstLastSeen]: firstLastSeenHost, + [HostsQueries.firstOrLastSeen]: firstOrLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsQueries.authentications]: authentications, [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts index 0cad31bffb2a19..a6d5dcdf022b59 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts @@ -5,9 +5,12 @@ * 2.0. */ -import { HostsQueries } from '../../../../../../../common/search_strategy'; +import { + HostFirstLastSeenRequestOptions, + HostsQueries, +} from '../../../../../../../common/search_strategy'; -export const mockOptions = { +export const mockOptions: HostFirstLastSeenRequestOptions = { defaultIndex: [ 'apm-*-transaction*', 'auditbeat-*', @@ -18,38 +21,164 @@ export const mockOptions = { 'winlogbeat-*', ], docValueFields: [], - factoryQueryType: HostsQueries.firstLastSeen, + factoryQueryType: HostsQueries.firstOrLastSeen, hostName: 'siem-kibana', + order: 'asc', }; -export const mockSearchStrategyResponse = { +export const mockSearchStrategyFirstSeenResponse = { isPartial: false, isRunning: false, rawResponse: { took: 230, timed_out: false, _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, - hits: { total: -1, max_score: 0, hits: [] }, - aggregations: { - lastSeen: { value: 1599554931759, value_as_string: '2020-09-08T08:48:51.759Z' }, - firstSeen: { value: 1591611722000, value_as_string: '2020-06-08T10:22:02.000Z' }, + hits: { + total: -1, + max_score: 0, + hits: [ + { + _type: 'doc', + _score: 0, + _index: 'auditbeat-7.8.0-2021.02.17-000012', + _id: 'nRIAs3cBX5UUcOOYANIW', + _source: { + '@timestamp': '2021-02-18T02:37:37.682Z', + }, + fields: { + '@timestamp': ['2021-02-18T02:37:37.682Z'], + }, + sort: ['1613615857682'], + }, + ], + }, + firstSeen: '2020-09-08T08:48:51.759Z', + }, + total: 21, + loaded: 21, +}; + +export const mockSearchStrategyLastSeenResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 230, + timed_out: false, + _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, + hits: { + total: -1, + max_score: 0, + hits: [ + { + _type: 'doc', + _score: 0, + _index: 'auditbeat-7.8.0-2021.02.17-000012', + _id: 'nRIAs3cBX5UUcOOYANIW', + _source: { + '@timestamp': '2021-02-18T02:37:37.682Z', + }, + fields: { + '@timestamp': ['2021-02-18T02:37:37.682Z'], + }, + sort: ['1613615857682'], + }, + ], + }, + lastSeen: '2020-09-08T08:48:51.759Z', + }, + total: 21, + loaded: 21, +}; + +export const formattedSearchStrategyFirstResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 230, + timed_out: false, + _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, + hits: { + total: -1, + max_score: 0, + hits: [ + { + _index: 'auditbeat-7.8.0-2021.02.17-000012', + _id: 'nRIAs3cBX5UUcOOYANIW', + _score: 0, + _source: { + '@timestamp': '2021-02-18T02:37:37.682Z', + }, + fields: { + '@timestamp': ['2021-02-18T02:37:37.682Z'], + }, + sort: ['1613615857682'], + }, + ], }, }, total: 21, loaded: 21, + inspect: { + dsl: [ + JSON.stringify( + { + allowNoIndices: true, + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + ignoreUnavailable: true, + track_total_hits: false, + body: { + query: { bool: { filter: [{ term: { 'host.name': 'siem-kibana' } }] } }, + _source: ['@timestamp'], + size: 1, + sort: [ + { + '@timestamp': { + order: 'asc', + }, + }, + ], + }, + }, + null, + 2 + ), + ], + }, + firstSeen: '2021-02-18T02:37:37.682Z', }; -export const formattedSearchStrategyResponse = { +export const formattedSearchStrategyLastResponse = { isPartial: false, isRunning: false, rawResponse: { took: 230, timed_out: false, _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, - hits: { total: -1, max_score: 0, hits: [] }, - aggregations: { - lastSeen: { value: 1599554931759, value_as_string: '2020-09-08T08:48:51.759Z' }, - firstSeen: { value: 1591611722000, value_as_string: '2020-06-08T10:22:02.000Z' }, + hits: { + total: -1, + max_score: 0, + hits: [ + { + _index: 'auditbeat-7.8.0-2021.02.17-000012', + _id: 'nRIAs3cBX5UUcOOYANIW', + _score: 0, + _source: { + '@timestamp': '2021-02-18T02:37:37.682Z', + }, + fields: { + '@timestamp': ['2021-02-18T02:37:37.682Z'], + }, + sort: ['1613615857682'], + }, + ], }, }, total: 21, @@ -71,12 +200,16 @@ export const formattedSearchStrategyResponse = { ignoreUnavailable: true, track_total_hits: false, body: { - aggregations: { - firstSeen: { min: { field: '@timestamp' } }, - lastSeen: { max: { field: '@timestamp' } }, - }, query: { bool: { filter: [{ term: { 'host.name': 'siem-kibana' } }] } }, - size: 0, + _source: ['@timestamp'], + size: 1, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], }, }, null, @@ -84,8 +217,7 @@ export const formattedSearchStrategyResponse = { ), ], }, - firstSeen: '2020-06-08T10:22:02.000Z', - lastSeen: '2020-09-08T08:48:51.759Z', + lastSeen: '2021-02-18T02:37:37.682Z', }; export const expectedDsl = { @@ -102,11 +234,9 @@ export const expectedDsl = { ignoreUnavailable: true, track_total_hits: false, body: { - aggregations: { - firstSeen: { min: { field: '@timestamp' } }, - lastSeen: { max: { field: '@timestamp' } }, - }, + _source: ['@timestamp'], query: { bool: { filter: [{ term: { 'host.name': 'siem-kibana' } }] } }, - size: 0, + size: 1, + sort: [{ '@timestamp': { order: 'asc' } }], }, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts index d416586f59cf7a..d0405d829b83db 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts @@ -5,32 +5,66 @@ * 2.0. */ -import * as buildQuery from './query.last_first_seen_host.dsl'; -import { firstLastSeenHost } from '.'; +import * as buildQuery from './query.first_or_last_seen_host.dsl'; +import { firstOrLastSeenHost } from '.'; import { mockOptions, - mockSearchStrategyResponse, - formattedSearchStrategyResponse, + mockSearchStrategyFirstSeenResponse, + mockSearchStrategyLastSeenResponse, + formattedSearchStrategyLastResponse, + formattedSearchStrategyFirstResponse, } from './__mocks__'; +import { HostFirstLastSeenRequestOptions } from '../../../../../../common/search_strategy'; describe('firstLastSeenHost search strategy', () => { - const buildFirstLastSeenHostQuery = jest.spyOn(buildQuery, 'buildFirstLastSeenHostQuery'); + describe('first seen search strategy', () => { + const buildFirstLastSeenHostQuery = jest.spyOn(buildQuery, 'buildFirstOrLastSeenHostQuery'); - afterEach(() => { - buildFirstLastSeenHostQuery.mockClear(); - }); + afterEach(() => { + buildFirstLastSeenHostQuery.mockClear(); + }); - describe('buildDsl', () => { - test('should build dsl query', () => { - firstLastSeenHost.buildDsl(mockOptions); - expect(buildFirstLastSeenHostQuery).toHaveBeenCalledWith(mockOptions); + describe('buildDsl', () => { + test('should build dsl query', () => { + firstOrLastSeenHost.buildDsl(mockOptions); + expect(buildFirstLastSeenHostQuery).toHaveBeenCalledWith(mockOptions); + }); + }); + + describe('parse', () => { + test('should parse data correctly', async () => { + const result = await firstOrLastSeenHost.parse( + mockOptions, + mockSearchStrategyFirstSeenResponse + ); + expect(result).toMatchObject(formattedSearchStrategyFirstResponse); + }); }); }); - describe('parse', () => { - test('should parse data correctly', async () => { - const result = await firstLastSeenHost.parse(mockOptions, mockSearchStrategyResponse); - expect(result).toMatchObject(formattedSearchStrategyResponse); + describe('last seen search strategy', () => { + const buildFirstLastSeenHostQuery = jest.spyOn(buildQuery, 'buildFirstOrLastSeenHostQuery'); + + afterEach(() => { + buildFirstLastSeenHostQuery.mockClear(); + }); + + describe('buildDsl', () => { + test('should build dsl query', () => { + const options: HostFirstLastSeenRequestOptions = { ...mockOptions, order: 'desc' }; + firstOrLastSeenHost.buildDsl(options); + expect(buildFirstLastSeenHostQuery).toHaveBeenCalledWith(options); + }); + }); + + describe('parse', () => { + test('should parse data correctly', async () => { + const result = await firstOrLastSeenHost.parse( + { ...mockOptions, order: 'desc' }, + mockSearchStrategyLastSeenResponse + ); + expect(result).toMatchObject(formattedSearchStrategyLastResponse); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts index fd0c92ee2bdf57..fee9a49e42c483 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts @@ -5,12 +5,10 @@ * 2.0. */ -import { get } from 'lodash/fp'; +import { getOr } from 'lodash/fp'; import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; import { - HostAggEsData, - HostAggEsItem, HostFirstLastSeenStrategyResponse, HostsQueries, HostFirstLastSeenRequestOptions, @@ -18,24 +16,40 @@ import { import { inspectStringifyObject } from '../../../../../utils/build_query'; import { SecuritySolutionFactory } from '../../types'; -import { buildFirstLastSeenHostQuery } from './query.last_first_seen_host.dsl'; +import { buildFirstOrLastSeenHostQuery } from './query.first_or_last_seen_host.dsl'; -export const firstLastSeenHost: SecuritySolutionFactory = { - buildDsl: (options: HostFirstLastSeenRequestOptions) => buildFirstLastSeenHostQuery(options), +export const firstOrLastSeenHost: SecuritySolutionFactory = { + buildDsl: (options: HostFirstLastSeenRequestOptions) => buildFirstOrLastSeenHostQuery(options), parse: async ( options: HostFirstLastSeenRequestOptions, - response: IEsSearchResponse + response: IEsSearchResponse ): Promise => { - const aggregations: HostAggEsItem = get('aggregations', response.rawResponse) || {}; + // First try to get the formatted field if it exists or not. + const formattedField: string | null = getOr( + null, + 'hits.hits[0].fields.@timestamp[0]', + response.rawResponse + ); + // If it doesn't exist, fall back on _source as a last try. + const seen: string | null = + formattedField || getOr(null, 'hits.hits[0]._source.@timestamp', response.rawResponse); + const inspect = { - dsl: [inspectStringifyObject(buildFirstLastSeenHostQuery(options))], + dsl: [inspectStringifyObject(buildFirstOrLastSeenHostQuery(options))], }; - return { - ...response, - inspect, - firstSeen: get('firstSeen.value_as_string', aggregations), - lastSeen: get('lastSeen.value_as_string', aggregations), - }; + if (options.order === 'asc') { + return { + ...response, + inspect, + firstSeen: seen, + }; + } else { + return { + ...response, + inspect, + lastSeen: seen, + }; + } }, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.test.ts similarity index 82% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.test.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.test.ts index be9b24308f1f6f..fe2cb96144e22e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { buildFirstLastSeenHostQuery as buildQuery } from './query.last_first_seen_host.dsl'; +import { buildFirstOrLastSeenHostQuery as buildQuery } from './query.first_or_last_seen_host.dsl'; import { mockOptions, expectedDsl } from './__mocks__'; describe('buildQuery', () => { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.ts similarity index 69% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.ts index d601a5905dd6e2..21876b9aad11be 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.ts @@ -6,14 +6,14 @@ */ import { isEmpty } from 'lodash/fp'; -import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; import { HostFirstLastSeenRequestOptions } from '../../../../../../common/search_strategy/security_solution/hosts'; -export const buildFirstLastSeenHostQuery = ({ +export const buildFirstOrLastSeenHostQuery = ({ hostName, defaultIndex, docValueFields, -}: HostFirstLastSeenRequestOptions): ISearchRequestParams => { + order, +}: HostFirstLastSeenRequestOptions) => { const filter = [{ term: { 'host.name': hostName } }]; const dslQuery = { @@ -23,12 +23,16 @@ export const buildFirstLastSeenHostQuery = ({ track_total_hits: false, body: { ...(!isEmpty(docValueFields) ? { docvalue_fields: docValueFields } : {}), - aggregations: { - firstSeen: { min: { field: '@timestamp' } }, - lastSeen: { max: { field: '@timestamp' } }, - }, query: { bool: { filter } }, - size: 0, + _source: ['@timestamp'], + size: 1, + sort: [ + { + '@timestamp': { + order, + }, + }, + ], }, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/last_event_time/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/last_event_time/index.ts index ab37c730c604d4..3b02e5621ed1ad 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/last_event_time/index.ts @@ -28,9 +28,19 @@ export const timelineEventsLastEventTime: SecuritySolutionTimelineFactory { + it('Make sure that we get First Seen for a Host without docValueFields', async () => { const { body: firstLastSeenHost } = await supertest .post('/internal/search/securitySolutionSearchStrategy/') .set('kbn-xsrf', 'true') .send({ - factoryQueryType: HostsQueries.firstLastSeen, + factoryQueryType: HostsQueries.firstOrLastSeen, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], docValueFields: [], hostName: 'zeek-sensor-san-francisco', + order: 'asc', }) .expect(200); - const expected = { - firstSeen: '2019-02-19T19:36:23.561Z', - lastSeen: '2019-02-19T20:42:33.561Z', - }; + expect(firstLastSeenHost.firstSeen).to.eql('2019-02-19T19:36:23.561Z'); + }); + + it('Make sure that we get Last Seen for a Host without docValueFields', async () => { + const { body: firstLastSeenHost } = await supertest + .post('/internal/search/securitySolutionSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: HostsQueries.firstOrLastSeen, + defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + docValueFields: [], + hostName: 'zeek-sensor-san-francisco', + order: 'desc', + }) + .expect(200); + expect(firstLastSeenHost.lastSeen).to.eql('2019-02-19T20:42:33.561Z'); + }); - expect(firstLastSeenHost.firstSeen).to.eql(expected.firstSeen); - expect(firstLastSeenHost.lastSeen).to.eql(expected.lastSeen); + it('Make sure that we get First Seen for a Host with docValueFields', async () => { + const { body: firstLastSeenHost } = await supertest + .post('/internal/search/securitySolutionSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: HostsQueries.firstOrLastSeen, + defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + docValueFields: [{ field: '@timestamp', format: 'epoch_millis' }], + hostName: 'zeek-sensor-san-francisco', + order: 'asc', + }) + .expect(200); + expect(firstLastSeenHost.firstSeen).to.eql(new Date('2019-02-19T19:36:23.561Z').valueOf()); + }); + + it('Make sure that we get Last Seen for a Host with docValueFields', async () => { + const { body: firstLastSeenHost } = await supertest + .post('/internal/search/securitySolutionSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: HostsQueries.firstOrLastSeen, + defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + docValueFields: [{ field: '@timestamp', format: 'epoch_millis' }], + hostName: 'zeek-sensor-san-francisco', + order: 'desc', + }) + .expect(200); + expect(firstLastSeenHost.lastSeen).to.eql(new Date('2019-02-19T20:42:33.561Z').valueOf()); }); }); } From 6defaed445007ae618f538a3aa825d6455b1ac27 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 18 Feb 2021 08:42:46 +0100 Subject: [PATCH 28/93] [APM] Update APM index pattern with added profile fields (#91738) --- src/plugins/apm_oss/server/tutorial/index_pattern.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/apm_oss/server/tutorial/index_pattern.json b/src/plugins/apm_oss/server/tutorial/index_pattern.json index 93a2393b70fa43..b0a1ac1ccd379b 100644 --- a/src/plugins/apm_oss/server/tutorial/index_pattern.json +++ b/src/plugins/apm_oss/server/tutorial/index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { - "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.parent.pgid\":{\"id\":\"string\"},\"process.parent.pid\":{\"id\":\"string\"},\"process.parent.ppid\":{\"id\":\"string\"},\"process.parent.thread.id\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"},\"view spans\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\"}}}", - "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.build.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"error.stack_trace.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.ingested\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reason\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.url\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.attributes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.drive_letter\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"file.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.build_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.strings\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.hive\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.value\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hosts\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.user\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.author\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.ruleset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.uuid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.cipher\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.ja3\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.server_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.supported_ciphers\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.client.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.established\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.next_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.resumed\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.ja3s\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.server.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.classification\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.enumeration\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.report_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.scanner.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.base\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.environmental\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.temporal\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.root\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cgroup.memory.mem.limit.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cgroup.memory.mem.usage.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"child.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.rows_affected\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.resource\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.cls\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.fid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.tbt\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.sum\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.max\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.histogram\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"metricset.period\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", + "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.parent.pgid\":{\"id\":\"string\"},\"process.parent.pid\":{\"id\":\"string\"},\"process.parent.ppid\":{\"id\":\"string\"},\"process.parent.thread.id\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"}}", + "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.build.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"error.stack_trace.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.ingested\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reason\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.url\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.attributes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.drive_letter\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"file.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.build_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.strings\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.hive\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.value\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hosts\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.user\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.author\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.ruleset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.uuid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.cipher\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.ja3\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.server_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.supported_ciphers\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.client.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.established\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.next_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.resumed\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.ja3s\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.server.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.classification\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.enumeration\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.report_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.scanner.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.base\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.environmental\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.temporal\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.service.selectors.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.root\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cgroup.memory.mem.limit.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cgroup.memory.mem.usage.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.wall.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"child.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.rows_affected\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.resource\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.cls\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.fid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.tbt\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.sum\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.max\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.histogram\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"metricset.period\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", "sourceFilters": "[{\"value\":\"sourcemap.sourcemap\"}]", "timeFieldName": "@timestamp" }, From 94f0bd900a75bcc4baaf6c5c6046f85f3e647588 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Thu, 18 Feb 2021 08:34:39 +0000 Subject: [PATCH 29/93] [Discover] Show unmapped fields when reading from source (#91719) * [Discover] Show unmapped fields when reading from source * Adding a unit test --- .../sidebar/lib/group_fields.test.ts | 29 ++++++++++++++++++- .../components/sidebar/lib/group_fields.tsx | 4 ++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts index 89980f7fd0f54a..9792e98ba84c7d 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts @@ -47,6 +47,7 @@ const fieldCounts = { category: 1, currency: 1, customer_birth_date: 1, + unknown_field: 1, }; describe('group_fields', function () { @@ -232,7 +233,7 @@ describe('group_fields', function () { const actual = groupFields( fieldsWithUnmappedField as IndexPatternField[], - ['customer_birth_date', 'currency', 'unknown'], + ['customer_birth_date', 'currency'], 5, fieldCounts, fieldFilterState, @@ -241,4 +242,30 @@ describe('group_fields', function () { ); expect(actual.unpopular).toEqual([]); }); + + it('includes unmapped fields when reading from source', function () { + const fieldFilterState = getDefaultFieldFilter(); + const fieldsWithUnmappedField = [...fields]; + fieldsWithUnmappedField.push({ + name: 'unknown_field', + type: 'unknown', + esTypes: ['unknown'], + count: 0, + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }); + + const actual = groupFields( + fieldsWithUnmappedField as IndexPatternField[], + ['customer_birth_date', 'currency'], + 5, + fieldCounts, + fieldFilterState, + false, + undefined + ); + expect(actual.unpopular.map((field) => field.name)).toEqual(['unknown_field']); + }); }); diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx index c7242a8518b553..eefb96b78aac66 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx +++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx @@ -64,7 +64,9 @@ export function groupFields( } else if (field.type !== '_source') { // do not show unmapped fields unless explicitly specified // do not add subfields to this list - if ((field.type !== 'unknown' || showUnmappedFields) && !isSubfield) { + if (useNewFieldsApi && (field.type !== 'unknown' || showUnmappedFields) && !isSubfield) { + result.unpopular.push(field); + } else if (!useNewFieldsApi) { result.unpopular.push(field); } } From 5f951bd5ea1a4312fba80c31abe77910132d3d2a Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Thu, 18 Feb 2021 03:49:45 -0500 Subject: [PATCH 30/93] Add missing SIEM app urls to searchDeepLinks (#91795) --- .../security_solution/public/app/search/index.ts | 10 +++++++++- x-pack/plugins/security_solution/public/plugin.tsx | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/app/search/index.ts b/x-pack/plugins/security_solution/public/app/search/index.ts index bddb43588bb6d1..110356269e8917 100644 --- a/x-pack/plugins/security_solution/public/app/search/index.ts +++ b/x-pack/plugins/security_solution/public/app/search/index.ts @@ -123,7 +123,15 @@ const securityDeepLinks: SecurityDeepLinks = { base: [], }, case: { - base: [], + base: [ + { + id: 'create', + title: i18n.translate('xpack.securitySolution.search.cases.create', { + defaultMessage: 'Create New Case', + }), + path: '/create', + }, + ], premium: [ { id: 'configure', diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index e8997cddc2cdcd..6aaad4a1571919 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -78,6 +78,7 @@ export class Plugin implements IPlugin(); private hostsUpdater$ = new Subject(); private networkUpdater$ = new Subject(); + private caseUpdater$ = new Subject(); private storage = new Storage(localStorage); private licensingSubscription: Subscription | null = null; @@ -279,6 +280,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { cases: subPlugin } = await this.subPlugins(); @@ -300,6 +302,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { management: managementSubPlugin } = await this.subPlugins(); @@ -380,6 +383,7 @@ export class Plugin implements IPlugin Date: Thu, 18 Feb 2021 11:52:14 +0100 Subject: [PATCH 31/93] [Logs UI] Wrap log stream embeddable with proper providers (#91725) This declares the correct dependencies for the `LogStreamEmbeddable` and replaces parts of the custom provider hierarchy used therein with the common Logs UI `CoreProviders`. --- .../infra/public/apps/common_providers.tsx | 6 ++-- .../log_stream/log_stream_embeddable.tsx | 34 +++++++++---------- .../log_stream_embeddable_factory.ts | 13 +++---- x-pack/plugins/infra/public/plugin.ts | 4 +-- 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/infra/public/apps/common_providers.tsx b/x-pack/plugins/infra/public/apps/common_providers.tsx index 972c6f8b14f8c4..64867c5743d0d4 100644 --- a/x-pack/plugins/infra/public/apps/common_providers.tsx +++ b/x-pack/plugins/infra/public/apps/common_providers.tsx @@ -7,18 +7,18 @@ import { AppMountParameters, CoreStart } from 'kibana/public'; import React, { useMemo } from 'react'; +import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; import { - useUiSetting$, KibanaContextProvider, + useUiSetting$, } from '../../../../../src/plugins/kibana_react/public'; -import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { TriggersAndActionsUIPublicPluginStart } from '../../../triggers_actions_ui/public'; import { createKibanaContextForPlugin } from '../hooks/use_kibana'; import { InfraClientStartDeps } from '../types'; import { HeaderActionMenuProvider } from '../utils/header_action_menu_provider'; import { NavigationWarningPromptProvider } from '../utils/navigation_warning_prompt'; import { TriggersActionsProvider } from '../utils/triggers_actions_context'; -import { Storage } from '../../../../../src/plugins/kibana_utils/public'; export const CommonInfraProviders: React.FC<{ appName: string; diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx index 766b8076bc93dc..e1427bc96e7e0b 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx @@ -5,19 +5,18 @@ * 2.0. */ +import { CoreStart } from 'kibana/public'; import React from 'react'; import ReactDOM from 'react-dom'; -import { CoreStart } from 'kibana/public'; - -import { I18nProvider } from '@kbn/i18n/react'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; import { Query, TimeRange } from '../../../../../../src/plugins/data/public'; import { Embeddable, EmbeddableInput, IContainer, } from '../../../../../../src/plugins/embeddable/public'; +import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; +import { CoreProviders } from '../../apps/common_providers'; +import { InfraClientStartDeps } from '../../types'; import { datemathToEpochMillis } from '../../utils/datemath'; import { LazyLogStreamWrapper } from './lazy_log_stream_wrapper'; @@ -33,7 +32,8 @@ export class LogStreamEmbeddable extends Embeddable { private node?: HTMLElement; constructor( - private services: CoreStart, + private core: CoreStart, + private pluginDeps: InfraClientStartDeps, initialInput: LogStreamEmbeddableInput, parent?: IContainer ) { @@ -73,20 +73,18 @@ export class LogStreamEmbeddable extends Embeddable { } ReactDOM.render( - + - -
- -
-
+
+ +
-
, + , this.node ); } diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts index 609a29e5842fe2..d621cae3e628cb 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts @@ -5,31 +5,32 @@ * 2.0. */ -import { CoreStart } from 'kibana/public'; +import { StartServicesAccessor } from 'kibana/public'; import { EmbeddableFactoryDefinition, IContainer, } from '../../../../../../src/plugins/embeddable/public'; +import { InfraClientStartDeps } from '../../types'; import { LogStreamEmbeddable, - LOG_STREAM_EMBEDDABLE, LogStreamEmbeddableInput, + LOG_STREAM_EMBEDDABLE, } from './log_stream_embeddable'; export class LogStreamEmbeddableFactoryDefinition implements EmbeddableFactoryDefinition { public readonly type = LOG_STREAM_EMBEDDABLE; - constructor(private getCoreServices: () => Promise) {} + constructor(private getStartServices: StartServicesAccessor) {} public async isEditable() { - const { application } = await this.getCoreServices(); + const [{ application }] = await this.getStartServices(); return application.capabilities.logs.save as boolean; } public async create(initialInput: LogStreamEmbeddableInput, parent?: IContainer) { - const services = await this.getCoreServices(); - return new LogStreamEmbeddable(services, initialInput, parent); + const [core, plugins] = await this.getStartServices(); + return new LogStreamEmbeddable(core, plugins, initialInput, parent); } public getDisplayName() { diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index d4bb83e8668ba5..07afbfdb5d4ed6 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -52,11 +52,9 @@ export class Plugin implements InfraClientPluginClass { }); } - const getCoreServices = async () => (await core.getStartServices())[0]; - pluginsSetup.embeddable.registerEmbeddableFactory( LOG_STREAM_EMBEDDABLE, - new LogStreamEmbeddableFactoryDefinition(getCoreServices) + new LogStreamEmbeddableFactoryDefinition(core.getStartServices) ); core.application.register({ From 1e3b13a55c2799878fa96bde878b8d8ac61cdd98 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 18 Feb 2021 14:02:43 +0200 Subject: [PATCH 32/93] [Security Solution][Case] Add the ability to mark cases in progress in cases table. (#89341) --- .../integration/cases/creation.spec.ts | 6 +- .../cypress/screens/all_cases.ts | 6 +- .../cases/components/all_cases/actions.tsx | 81 ++++++++++------ .../cases/components/all_cases/index.test.tsx | 43 +++++++++ .../cases/components/bulk_actions/index.tsx | 95 +++++++++++++------ .../components/bulk_actions/translations.ts | 14 --- .../components/property_actions/constants.ts | 8 -- .../cases/components/status/button.test.tsx | 4 +- .../public/cases/components/status/button.tsx | 2 +- .../public/cases/components/status/config.ts | 43 ++++++++- .../cases/components/status/translations.ts | 28 ++++++ .../public/cases/containers/translations.ts | 15 ++- .../cases/containers/use_bulk_update_case.tsx | 30 +++++- .../public/cases/translations.ts | 4 + 14 files changed, 279 insertions(+), 100 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/cases/components/property_actions/constants.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts index 5a2cf5408b04de..64ce6be9ec457b 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts @@ -8,11 +8,10 @@ import { case1 } from '../../objects/case'; import { - ALL_CASES_CLOSE_ACTION, ALL_CASES_CLOSED_CASES_STATS, ALL_CASES_COMMENTS_COUNT, - ALL_CASES_DELETE_ACTION, ALL_CASES_IN_PROGRESS_CASES_STATS, + ALL_CASES_ITEM_ACTIONS_BTN, ALL_CASES_NAME, ALL_CASES_OPEN_CASES_COUNT, ALL_CASES_OPEN_CASES_STATS, @@ -91,8 +90,7 @@ describe('Cases', () => { cy.get(ALL_CASES_COMMENTS_COUNT).should('have.text', '0'); cy.get(ALL_CASES_OPENED_ON).should('include.text', 'ago'); cy.get(ALL_CASES_SERVICE_NOW_INCIDENT).should('have.text', 'Not pushed'); - cy.get(ALL_CASES_DELETE_ACTION).should('exist'); - cy.get(ALL_CASES_CLOSE_ACTION).should('exist'); + cy.get(ALL_CASES_ITEM_ACTIONS_BTN).should('exist'); goToCaseDetails(); diff --git a/x-pack/plugins/security_solution/cypress/screens/all_cases.ts b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts index 06d1a9fca91c67..e9c5ff89dd8c4b 100644 --- a/x-pack/plugins/security_solution/cypress/screens/all_cases.ts +++ b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts @@ -9,8 +9,6 @@ export const ALL_CASES_CASE = (id: string) => { return `[data-test-subj="cases-table-row-${id}"]`; }; -export const ALL_CASES_CLOSE_ACTION = '[data-test-subj="action-close"]'; - export const ALL_CASES_CLOSED_CASES_STATS = '[data-test-subj="closedStatsHeader"]'; export const ALL_CASES_COMMENTS_COUNT = '[data-test-subj="case-table-column-commentCount"]'; @@ -19,10 +17,10 @@ export const ALL_CASES_CREATE_NEW_CASE_BTN = '[data-test-subj="createNewCaseBtn" export const ALL_CASES_CREATE_NEW_CASE_TABLE_BTN = '[data-test-subj="cases-table-add-case"]'; -export const ALL_CASES_DELETE_ACTION = '[data-test-subj="action-delete"]'; - export const ALL_CASES_IN_PROGRESS_CASES_STATS = '[data-test-subj="inProgressStatsHeader"]'; +export const ALL_CASES_ITEM_ACTIONS_BTN = '[data-test-subj="euiCollapsedItemActionsButton"]'; + export const ALL_CASES_NAME = '[data-test-subj="case-details-link"]'; export const ALL_CASES_OPEN_CASES_COUNT = '[data-test-subj="case-status-filter"]'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx index 8178e7e9f9e8f8..66563deae54227 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx @@ -11,6 +11,7 @@ import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_t import { CaseStatuses } from '../../../../../case/common/api'; import { Case, SubCase } from '../../containers/types'; import { UpdateCase } from '../../containers/use_get_cases'; +import { statuses } from '../status'; import * as i18n from './translations'; interface GetActions { @@ -26,43 +27,67 @@ export const getActions = ({ caseStatus, dispatchUpdate, deleteCaseOnClick, -}: GetActions): Array> => [ - { - description: i18n.DELETE_CASE, - icon: 'trash', - name: i18n.DELETE_CASE, - onClick: deleteCaseOnClick, - type: 'icon', - 'data-test-subj': 'action-delete', - }, - { - available: (item) => caseStatus === CaseStatuses.open && !hasSubCases(item.subCases), - description: i18n.CLOSE_CASE, - icon: 'folderCheck', - name: i18n.CLOSE_CASE, +}: GetActions): Array> => { + const openCaseAction = { + available: (item: Case) => caseStatus !== CaseStatuses.open && !hasSubCases(item.subCases), + description: statuses[CaseStatuses.open].actions.single.title, + icon: statuses[CaseStatuses.open].icon, + name: statuses[CaseStatuses.open].actions.single.title, onClick: (theCase: Case) => dispatchUpdate({ updateKey: 'status', - updateValue: CaseStatuses.closed, + updateValue: CaseStatuses.open, caseId: theCase.id, version: theCase.version, }), - type: 'icon', - 'data-test-subj': 'action-close', - }, - { - available: (item) => caseStatus !== CaseStatuses.open && !hasSubCases(item.subCases), - description: i18n.REOPEN_CASE, - icon: 'folderExclamation', - name: i18n.REOPEN_CASE, + type: 'icon' as const, + 'data-test-subj': 'action-open', + }; + + const makeInProgressAction = { + available: (item: Case) => + caseStatus !== CaseStatuses['in-progress'] && !hasSubCases(item.subCases), + description: statuses[CaseStatuses['in-progress']].actions.single.title, + icon: statuses[CaseStatuses['in-progress']].icon, + name: statuses[CaseStatuses['in-progress']].actions.single.title, onClick: (theCase: Case) => dispatchUpdate({ updateKey: 'status', - updateValue: CaseStatuses.open, + updateValue: CaseStatuses['in-progress'], caseId: theCase.id, version: theCase.version, }), - type: 'icon', - 'data-test-subj': 'action-open', - }, -]; + type: 'icon' as const, + 'data-test-subj': 'action-in-progress', + }; + + const closeCaseAction = { + available: (item: Case) => caseStatus !== CaseStatuses.closed && !hasSubCases(item.subCases), + description: statuses[CaseStatuses.closed].actions.single.title, + icon: statuses[CaseStatuses.closed].icon, + name: statuses[CaseStatuses.closed].actions.single.title, + onClick: (theCase: Case) => + dispatchUpdate({ + updateKey: 'status', + updateValue: CaseStatuses.closed, + caseId: theCase.id, + version: theCase.version, + }), + type: 'icon' as const, + 'data-test-subj': 'action-close', + }; + + return [ + { + description: i18n.DELETE_CASE, + icon: 'trash', + name: i18n.DELETE_CASE, + onClick: deleteCaseOnClick, + type: 'icon', + 'data-test-subj': 'action-delete', + }, + openCaseAction, + makeInProgressAction, + closeCaseAction, + ]; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx index a44ccd2384843c..a145bdf117813e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx @@ -281,6 +281,7 @@ describe('AllCases', () => { ); await waitFor(() => { + wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click'); wrapper.find('[data-test-subj="action-close"]').first().simulate('click'); const firstCase = useGetCasesMockState.data.cases[0]; expect(dispatchUpdateCaseProperty).toBeCalledWith({ @@ -305,6 +306,7 @@ describe('AllCases', () => { ); await waitFor(() => { + wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click'); wrapper.find('[data-test-subj="action-open"]').first().simulate('click'); const firstCase = useGetCasesMockState.data.cases[0]; expect(dispatchUpdateCaseProperty).toBeCalledWith({ @@ -317,6 +319,26 @@ describe('AllCases', () => { }); }); + it('put case in progress when row action icon clicked', async () => { + const wrapper = mount( + + + + ); + await waitFor(() => { + wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click'); + wrapper.find('[data-test-subj="action-in-progress"]').first().simulate('click'); + const firstCase = useGetCasesMockState.data.cases[0]; + expect(dispatchUpdateCaseProperty).toBeCalledWith({ + caseId: firstCase.id, + updateKey: 'status', + updateValue: CaseStatuses['in-progress'], + refetchCasesStatus: fetchCasesStatus, + version: firstCase.version, + }); + }); + }); + it('Bulk delete', async () => { useGetCasesMock.mockReturnValue({ ...defaultGetCases, @@ -395,6 +417,27 @@ describe('AllCases', () => { }); }); + it('Bulk in-progress status update', async () => { + useGetCasesMock.mockReturnValue({ + ...defaultGetCases, + selectedCases: useGetCasesMockState.data.cases, + }); + + const wrapper = mount( + + + + ); + await waitFor(() => { + wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click'); + wrapper.find('[data-test-subj="cases-bulk-in-progress-button"]').first().simulate('click'); + expect(updateBulkStatus).toBeCalledWith( + useGetCasesMockState.data.cases, + CaseStatuses['in-progress'] + ); + }); + }); + it('isDeleted is true, refetch', async () => { useDeleteCasesMock.mockReturnValue({ ...defaultDeleteCases, diff --git a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx index f9722b3903b122..ec3b391cdcbfee 100644 --- a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx @@ -9,10 +9,11 @@ import React from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; import { CaseStatuses } from '../../../../../case/common/api'; +import { statuses } from '../status'; import * as i18n from './translations'; interface GetBulkItems { - caseStatus: string; + caseStatus: CaseStatuses; closePopover: () => void; deleteCasesAction: (cases: string[]) => void; selectedCaseIds: string[]; @@ -26,34 +27,72 @@ export const getBulkItems = ({ selectedCaseIds, updateCaseStatus, }: GetBulkItems) => { + let statusMenuItems: JSX.Element[] = []; + + const openMenuItem = ( + { + closePopover(); + updateCaseStatus(CaseStatuses.open); + }} + > + {statuses[CaseStatuses.open].actions.bulk.title} + + ); + + const inProgressMenuItem = ( + { + closePopover(); + updateCaseStatus(CaseStatuses['in-progress']); + }} + > + {statuses[CaseStatuses['in-progress']].actions.bulk.title} + + ); + + const closeMenuItem = ( + { + closePopover(); + updateCaseStatus(CaseStatuses.closed); + }} + > + {statuses[CaseStatuses.closed].actions.bulk.title} + + ); + + switch (caseStatus) { + case CaseStatuses.open: + statusMenuItems = [inProgressMenuItem, closeMenuItem]; + break; + + case CaseStatuses['in-progress']: + statusMenuItems = [openMenuItem, closeMenuItem]; + break; + + case CaseStatuses.closed: + statusMenuItems = [openMenuItem, inProgressMenuItem]; + break; + + default: + break; + } + return [ - caseStatus === CaseStatuses.open ? ( - { - closePopover(); - updateCaseStatus(CaseStatuses.closed); - }} - > - {i18n.BULK_ACTION_CLOSE_SELECTED} - - ) : ( - { - closePopover(); - updateCaseStatus(CaseStatuses.open); - }} - > - {i18n.BULK_ACTION_OPEN_SELECTED} - - ), + ...statusMenuItems, { expect( wrapper.find(`[data-test-subj="case-view-status-action-button"]`).first().prop('iconType') - ).toBe('folderCheck'); + ).toBe('folderClosed'); }); it('it renders the correct button icon: status closed', () => { @@ -50,7 +50,7 @@ describe('StatusActionButton', () => { expect( wrapper.find(`[data-test-subj="case-view-status-action-button"]`).first().prop('iconType') - ).toBe('folderCheck'); + ).toBe('folderOpen'); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/status/button.tsx b/x-pack/plugins/security_solution/public/cases/components/status/button.tsx index 2ec0ccd245b1ef..4ee69766fe1287 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/button.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/status/button.tsx @@ -40,7 +40,7 @@ const StatusActionButtonComponent: React.FC = ({ return ( i18n.translate('xpack.securitySolution.containers.case.reopenedCases', { values: { caseTitle, totalCases }, - defaultMessage: 'Reopened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', + defaultMessage: 'Opened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', + }); + +export const MARK_IN_PROGRESS_CASES = ({ + totalCases, + caseTitle, +}: { + totalCases: number; + caseTitle?: string; +}) => + i18n.translate('xpack.securitySolution.containers.case.markInProgressCases', { + values: { caseTitle, totalCases }, + defaultMessage: + 'Marked {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}} as in progress', }); export const SUCCESS_SEND_TO_EXTERNAL_SERVICE = (serviceName: string) => diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx index 5fd181a4bbd414..0fe45aaab799b9 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx @@ -62,6 +62,24 @@ export interface UseUpdateCases extends UpdateState { dispatchResetIsUpdated: () => void; } +const getStatusToasterMessage = ( + status: CaseStatuses, + messageArgs: { + totalCases: number; + caseTitle?: string; + } +): string => { + if (status === CaseStatuses.open) { + return i18n.REOPENED_CASES(messageArgs); + } else if (status === CaseStatuses['in-progress']) { + return i18n.MARK_IN_PROGRESS_CASES(messageArgs); + } else if (status === CaseStatuses.closed) { + return i18n.CLOSED_CASES(messageArgs); + } + + return ''; +}; + export const useUpdateCases = (): UseUpdateCases => { const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, @@ -70,7 +88,7 @@ export const useUpdateCases = (): UseUpdateCases => { }); const [, dispatchToaster] = useStateToaster(); - const dispatchUpdateCases = useCallback((cases: BulkUpdateStatus[]) => { + const dispatchUpdateCases = useCallback((cases: BulkUpdateStatus[], action: string) => { let cancel = false; const abortCtrl = new AbortController(); @@ -83,14 +101,16 @@ export const useUpdateCases = (): UseUpdateCases => { const firstTitle = patchResponse[0].title; dispatch({ type: 'FETCH_SUCCESS', payload: true }); + const messageArgs = { totalCases: resultCount, caseTitle: resultCount === 1 ? firstTitle : '', }; + const message = - resultCount && patchResponse[0].status === CaseStatuses.open - ? i18n.REOPENED_CASES(messageArgs) - : i18n.CLOSED_CASES(messageArgs); + action === 'status' + ? getStatusToasterMessage(patchResponse[0].status, messageArgs) + : ''; displaySuccessToast(message, dispatchToaster); } @@ -123,7 +143,7 @@ export const useUpdateCases = (): UseUpdateCases => { id: theCase.id, version: theCase.version, })); - dispatchUpdateCases(updateCasesStatus); + dispatchUpdateCases(updateCasesStatus, 'status'); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { ...state, updateBulkStatus, dispatchResetIsUpdated }; diff --git a/x-pack/plugins/security_solution/public/cases/translations.ts b/x-pack/plugins/security_solution/public/cases/translations.ts index 156cb91994468e..caaa1f6e248ea6 100644 --- a/x-pack/plugins/security_solution/public/cases/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/translations.ts @@ -131,6 +131,10 @@ export const REOPEN_CASE = i18n.translate('xpack.securitySolution.case.caseView. defaultMessage: 'Reopen case', }); +export const OPEN_CASE = i18n.translate('xpack.securitySolution.case.caseView.openCase', { + defaultMessage: 'Open case', +}); + export const CASE_NAME = i18n.translate('xpack.securitySolution.case.caseView.caseName', { defaultMessage: 'Case name', }); From 2aaeea7ce83a5acaf452d79c9ec4218808b033d2 Mon Sep 17 00:00:00 2001 From: Bhavya RM Date: Thu, 18 Feb 2021 07:56:18 -0500 Subject: [PATCH 33/93] Unskip import saved objects test --- test/functional/apps/management/_import_objects.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts index e2a056359b48e2..ca8d8c392ce49b 100644 --- a/test/functional/apps/management/_import_objects.ts +++ b/test/functional/apps/management/_import_objects.ts @@ -24,7 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); // FLAKY: https://github.com/elastic/kibana/issues/89478 - describe.skip('import objects', function describeIndexTests() { + describe('import objects', function describeIndexTests() { describe('.ndjson file', () => { beforeEach(async function () { await esArchiver.load('management'); From 8c9eaa2fbaf649d5d215b2ac86b8a636c4ff8bb9 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Thu, 18 Feb 2021 08:07:14 -0500 Subject: [PATCH 34/93] [App Search] First cut of the Relevance Tuning UI (#90621) --- .../relevance_tuning/boost_icon.test.tsx | 26 ++++ .../relevance_tuning/boost_icon.tsx | 28 ++++ .../components/relevance_tuning/constants.ts | 41 ++++- .../relevance_tuning.test.tsx | 20 ++- .../relevance_tuning/relevance_tuning.tsx | 44 +++++- .../relevance_tuning_form/index.ts | 8 + .../relevance_tuning_form.scss | 20 +++ .../relevance_tuning_form.test.tsx | 140 ++++++++++++++++++ .../relevance_tuning_form.tsx | 106 +++++++++++++ .../relevance_tuning_item.test.tsx | 126 ++++++++++++++++ .../relevance_tuning_item.tsx | 60 ++++++++ .../boosts/boost_item.tsx | 53 +++++++ .../boosts/boosts.scss | 28 ++++ .../boosts/boosts.test.tsx | 71 +++++++++ .../boosts/boosts.tsx | 118 +++++++++++++++ .../boosts/get_boost_summary.test.ts | 73 +++++++++ .../boosts/get_boost_summary.ts | 18 +++ .../boosts/index.ts | 8 + .../relevance_tuning_item_content/index.ts | 8 + .../relevance_tuning_item_content.scss | 6 + .../relevance_tuning_item_content.test.tsx | 65 ++++++++ .../relevance_tuning_item_content.tsx | 39 +++++ .../text_search_toggle.test.tsx | 122 +++++++++++++++ .../text_search_toggle.tsx | 62 ++++++++ .../weight_slider.test.tsx | 50 +++++++ .../weight_slider.tsx | 53 +++++++ .../relevance_tuning_form/value_badge.scss | 23 +++ .../relevance_tuning_form/value_badge.tsx | 22 +++ .../relevance_tuning_logic.test.ts | 122 +++++++++------ .../relevance_tuning_logic.ts | 21 ++- .../components/relevance_tuning/types.ts | 33 +++-- .../components/relevance_tuning/utils.test.ts | 45 +++--- 32 files changed, 1564 insertions(+), 95 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boost_item.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.test.tsx new file mode 100644 index 00000000000000..fd567f52ada249 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.test.tsx @@ -0,0 +1,26 @@ +/* + * 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 React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiToken } from '@elastic/eui'; + +import { BoostIcon } from './boost_icon'; +import { BoostType } from './types'; + +describe('BoostIcon', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders a token according to the provided type', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiToken).prop('iconType')).toBe('tokenNumber'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.tsx new file mode 100644 index 00000000000000..2570a29274d069 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boost_icon.tsx @@ -0,0 +1,28 @@ +/* + * 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 React from 'react'; + +import { EuiToken } from '@elastic/eui'; + +import { BOOST_TYPE_TO_ICON_MAP } from './constants'; +import { BoostType } from './types'; + +interface Props { + type: BoostType; +} + +export const BoostIcon: React.FC = ({ type }) => { + return ( + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts index 211995b2a7d188..9fdbb8e979b316 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts @@ -7,33 +7,66 @@ import { i18n } from '@kbn/i18n'; +import { BoostType } from './types'; + +export const FIELD_FILTER_CUTOFF = 10; + export const RELEVANCE_TUNING_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.title', { defaultMessage: 'Relevance Tuning' } ); export const UPDATE_SUCCESS_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.relevanceTuning.messages.updateSuccess', + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.updateSuccess', { defaultMessage: 'Relevance successfully tuned. The changes will impact your results shortly.', } ); export const DELETE_SUCCESS_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.relevanceTuning.messages.deleteSuccess', + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteSuccess', { defaultMessage: 'Relevance has been reset to default values. The change will impact your results shortly.', } ); export const RESET_CONFIRMATION_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.relevanceTuning.messages.resetConfirmation', + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.resetConfirmation', { defaultMessage: 'Are you sure you want to restore relevance defaults?', } ); export const DELETE_CONFIRMATION_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.relevanceTuning.messages.deleteConfirmation', + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteConfirmation', { defaultMessage: 'Are you sure you want to delete this boost?', } ); +export const PROXIMITY_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximityDropDownOptionLabel', + { + defaultMessage: 'Proximity', + } +); +export const FUNCTIONAL_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.functionalDropDownOptionLabel', + { + defaultMessage: 'Functional', + } +); +export const VALUE_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.valueDropDownOptionLabel', + { + defaultMessage: 'Value', + } +); +export const BOOST_TYPE_TO_DISPLAY_MAP = { + [BoostType.Proximity]: PROXIMITY_DISPLAY, + [BoostType.Functional]: FUNCTIONAL_DISPLAY, + [BoostType.Value]: VALUE_DISPLAY, +}; + +export const BOOST_TYPE_TO_ICON_MAP = { + [BoostType.Value]: 'tokenNumber', + [BoostType.Functional]: 'tokenFunction', + [BoostType.Proximity]: 'tokenGeo', +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx index ea8d43d183ef0f..85cf3dd8a68c97 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx @@ -4,20 +4,34 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import '../../../__mocks__/shallow_useeffect.mock'; +import { setMockActions } from '../../../__mocks__/kea.mock'; import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { RelevanceTuning } from './relevance_tuning'; +import { RelevanceTuningForm } from './relevance_tuning_form'; describe('RelevanceTuning', () => { + let wrapper: ShallowWrapper; + + const actions = { + initializeRelevanceTuning: jest.fn(), + }; + beforeEach(() => { jest.clearAllMocks(); + setMockActions(actions); + wrapper = shallow(); }); it('renders', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); + expect(wrapper.find(RelevanceTuningForm).exists()).toBe(true); + }); + + it('initializes relevance tuning data', () => { + expect(actions.initializeRelevanceTuning).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx index 83e83c0f9ea43e..f65a86b1e02f0a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx @@ -5,26 +5,41 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; + +import { useActions } from 'kea'; import { EuiPageHeader, EuiPageHeaderSection, EuiTitle, - EuiPageContentBody, - EuiPageContent, + EuiText, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiTextColor, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + import { FlashMessages } from '../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { RELEVANCE_TUNING_TITLE } from './constants'; +import { RelevanceTuningForm } from './relevance_tuning_form'; +import { RelevanceTuningLogic } from './relevance_tuning_logic'; interface Props { engineBreadcrumb: string[]; } export const RelevanceTuning: React.FC = ({ engineBreadcrumb }) => { + const { initializeRelevanceTuning } = useActions(RelevanceTuningLogic); + + useEffect(() => { + initializeRelevanceTuning(); + }, []); + return ( <> @@ -33,13 +48,26 @@ export const RelevanceTuning: React.FC = ({ engineBreadcrumb }) => {

{RELEVANCE_TUNING_TITLE}

+ + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.description', + { + defaultMessage: 'Set field weights and boosts', + } + )} + + - - - - - + + + + + + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/index.ts new file mode 100644 index 00000000000000..89e344860b2eb8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/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 { RelevanceTuningForm } from './relevance_tuning_form'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss new file mode 100644 index 00000000000000..749fca6f798110 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss @@ -0,0 +1,20 @@ +.relevanceTuningForm { + &__item { + width: 100%; + margin-left: $euiSizeS; + } + + &__panel + &__panel { + margin-top: $euiSizeM; + } + + &__panel .euiAccordion__button { + &:hover, + &:focus { + text-decoration: none; + h3 { + text-decoration: underline; + } + } + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx new file mode 100644 index 00000000000000..3965e9e81d1ba3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx @@ -0,0 +1,140 @@ +/* + * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow, mount, ReactWrapper, ShallowWrapper } from 'enzyme'; + +import { EuiFieldSearch } from '@elastic/eui'; + +import { BoostType } from '../types'; + +import { RelevanceTuningForm } from './relevance_tuning_form'; +import { RelevanceTuningItem } from './relevance_tuning_item'; + +describe('RelevanceTuningForm', () => { + const values = { + filterInputValue: '', + schemaFields: ['foo', 'bar', 'baz'], + filteredSchemaFields: ['foo', 'bar'], + schema: { + foo: 'text', + bar: 'number', + }, + searchSettings: { + boosts: { + foo: [ + { + factor: 2, + type: BoostType.Value, + }, + ], + }, + search_fields: { + bar: { + weight: 1, + }, + }, + }, + }; + const actions = { + setFilterValue: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockActions(actions); + }); + + describe('fields', () => { + let wrapper: ReactWrapper; + let relevantTuningItems: any; + + beforeAll(() => { + setMockValues(values); + + wrapper = mount(); + relevantTuningItems = wrapper.find(RelevanceTuningItem); + }); + + it('renders a list of fields that may or may not have been filterd by user input', () => { + // The length is 2 because we're only pulling values from `filteredSchemaFields`, which + // is the list of schema fields that has been filtered by user input down to 2 + expect(relevantTuningItems.length).toBe(2); + }); + + it('will pass the schema field name in the "name" prop of each list item', () => { + expect(relevantTuningItems.at(0).prop('name')).toBe('foo'); + expect(relevantTuningItems.at(1).prop('name')).toBe('bar'); + }); + + it('will pass the schema type of the field in the "type" prop of each list item', () => { + expect(relevantTuningItems.at(0).prop('type')).toBe('text'); + expect(relevantTuningItems.at(1).prop('type')).toBe('number'); + }); + + it('will pass a list of boosts in the "boosts" field of each list item, or undefined if none exist', () => { + expect(relevantTuningItems.at(0).prop('boosts')).toEqual([ + { + factor: 2, + type: BoostType.Value, + }, + ]); + expect(relevantTuningItems.at(1).prop('boosts')).toBeUndefined(); + }); + + it('will pass the search_field configuration for the field in the "field" prop of each list item, or undefined if none exists', () => { + expect(relevantTuningItems.at(0).prop('field')).toBeUndefined(); + expect(relevantTuningItems.at(1).prop('field')).toEqual({ + weight: 1, + }); + }); + }); + + describe('field filtering', () => { + let searchField: ShallowWrapper; + + beforeEach(() => { + setMockValues({ + ...values, + filterInputValue: 'test', + schemaFields: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'], + }); + const wrapper = shallow(); + searchField = wrapper.find(EuiFieldSearch); + }); + + it('renders an input box for filtering the field list in case there is a large quantity of fields', () => { + expect(searchField.exists()).toBe(true); + }); + + it('initializes the input box with the user input value stored in state', () => { + expect(searchField.prop('value')).toBe('test'); + }); + + it('updates the user input value stored in state whenever the input box value changes', () => { + searchField.simulate('change', { + target: { + value: 'new value', + }, + }); + + expect(actions.setFilterValue).toHaveBeenCalledWith('new value'); + }); + + it('will not render a field filter if there are 10 or less fields', () => { + setMockValues({ + ...values, + schemaFields: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], + }); + const wrapper = shallow(); + expect(wrapper.find(EuiFieldSearch).exists()).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx new file mode 100644 index 00000000000000..e39c93fd5de3cb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx @@ -0,0 +1,106 @@ +/* + * 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 React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, + EuiFieldSearch, + EuiSpacer, + EuiAccordion, + EuiPanel, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { FIELD_FILTER_CUTOFF } from '../constants'; +import { RelevanceTuningLogic } from '../relevance_tuning_logic'; + +import { RelevanceTuningItem } from './relevance_tuning_item'; +import { RelevanceTuningItemContent } from './relevance_tuning_item_content'; + +import './relevance_tuning_form.scss'; + +export const RelevanceTuningForm: React.FC = () => { + const { + filterInputValue, + schemaFields, + filteredSchemaFields, + schema, + searchSettings, + } = useValues(RelevanceTuningLogic); + const { setFilterValue } = useActions(RelevanceTuningLogic); + + return ( +
+
+ {/* TODO SchemaConflictCallout */} + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.title', + { + defaultMessage: 'Manage fields', + } + )} +

+
+
+
+ {schemaFields.length > FIELD_FILTER_CUTOFF && ( + setFilterValue(e.target.value)} + placeholder={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.filterPlaceholder', + { + defaultMessage: 'Filter {schemaFieldsLength} fields...', + values: { + schemaFieldsLength: schemaFields.length, + }, + } + )} + fullWidth + /> + )} + + {filteredSchemaFields.map((fieldName) => ( + + + } + paddingSize="s" + > + + + + ))} + +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx new file mode 100644 index 00000000000000..6043e7ae65b268 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx @@ -0,0 +1,126 @@ +/* + * 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 React from 'react'; + +import { shallow } from 'enzyme'; + +import { SchemaTypes } from '../../../../shared/types'; + +import { BoostIcon } from '../boost_icon'; +import { Boost, BoostType, SearchField } from '../types'; + +import { RelevanceTuningItem } from './relevance_tuning_item'; +import { ValueBadge } from './value_badge'; + +describe('RelevanceTuningItem', () => { + const props = { + name: 'foo', + type: 'text' as SchemaTypes, + boosts: [ + { + factor: 2, + type: BoostType.Value, + }, + ], + field: { + weight: 1, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('boosts prop', () => { + const renderComponentWithBoostsConfig = (boosts?: Boost[]) => { + return shallow( + + ); + }; + + describe('when there are boosts for this field', () => { + it('renders an icon for each boost that is applied', () => { + const wrapper = renderComponentWithBoostsConfig([ + { + factor: 2, + type: BoostType.Value, + }, + { + factor: 3, + type: BoostType.Proximity, + }, + ]); + expect(wrapper.find(BoostIcon).length).toBe(2); + expect(wrapper.find(BoostIcon).map((euiToken) => euiToken.prop('type'))).toEqual([ + BoostType.Value, + BoostType.Proximity, + ]); + }); + }); + + describe('when there are no boosts for this field', () => { + const wrapper = renderComponentWithBoostsConfig(); + + it('renders an icon for each boost that is applied', () => { + expect(wrapper.find(BoostIcon).length).toBe(0); + }); + }); + }); + + describe('field prop', () => { + const renderComponentWithFieldConfig = (field?: SearchField) => { + return shallow( + + ); + }; + + describe('when weight is set to any positive number', () => { + const wrapper = renderComponentWithFieldConfig({ + weight: 1, + }); + + it('will show the weight with an "enabled" style', () => { + const valueBadge = wrapper.find(ValueBadge); + expect(valueBadge.dive().text()).toContain('1'); + expect(valueBadge.prop('disabled')).toBe(false); + }); + }); + + describe('when weight set to "0", which means this field will not be searched', () => { + const wrapper = renderComponentWithFieldConfig({ + weight: 0, + }); + + it('will show 0 with a "disabled" style', () => { + const valueBadge = wrapper.find(ValueBadge); + expect(valueBadge.dive().text()).toContain('0'); + expect(valueBadge.prop('disabled')).toBe(true); + }); + }); + + describe('when there is no weight set, which means this field will not be searched', () => { + const wrapper = renderComponentWithFieldConfig(); + + it('will show "0" with a "disabled" style', () => { + const valueBadge = wrapper.find(ValueBadge); + expect(valueBadge.dive().text()).toContain('0'); + expect(valueBadge.prop('disabled')).toBe(true); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx new file mode 100644 index 00000000000000..38cec4825cfe7f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx @@ -0,0 +1,60 @@ +/* + * 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 React from 'react'; + +import { EuiText, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiTextColor, EuiIcon } from '@elastic/eui'; + +import { SchemaTypes } from '../../../../shared/types'; + +import { BoostIcon } from '../boost_icon'; +import { Boost, SearchField } from '../types'; + +import { ValueBadge } from './value_badge'; + +interface Props { + name: string; + type: SchemaTypes; + boosts?: Boost[]; + field?: SearchField; +} + +export const RelevanceTuningItem: React.FC = ({ name, type, boosts = [], field }) => { + return ( + + + +

{name}

+
+ + {type} + +
+ + + {boosts.map((boost, index) => ( + + + + ))} + + + + {!!field ? field.weight : 0} + + + + +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boost_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boost_item.tsx new file mode 100644 index 00000000000000..e5a76bc586b80c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boost_item.tsx @@ -0,0 +1,53 @@ +/* + * 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 React, { useMemo } from 'react'; + +import { EuiFlexItem, EuiAccordion, EuiFlexGroup, EuiHideFor } from '@elastic/eui'; + +import { BoostIcon } from '../../../boost_icon'; +import { BOOST_TYPE_TO_DISPLAY_MAP } from '../../../constants'; +import { Boost } from '../../../types'; +import { ValueBadge } from '../../value_badge'; + +import { getBoostSummary } from './get_boost_summary'; + +interface Props { + boost: Boost; + id: string; +} + +export const BoostItem: React.FC = ({ id, boost }) => { + const summary = useMemo(() => getBoostSummary(boost), [boost]); + + return ( + + + + + + + {BOOST_TYPE_TO_DISPLAY_MAP[boost.type]} + + {summary} + + + + + {boost.factor} + + + } + paddingSize="s" + /> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.scss new file mode 100644 index 00000000000000..53b3c233301b08 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.scss @@ -0,0 +1,28 @@ +.boosts { + &__select { + min-width: $euiSizeXXL * 4; + } + + &__itemContent { + width: 100%; + } + + &__item { + margin-top: $euiSize; + + & + & { + margin-top: $euiSizeS; + } + } +} + +.boostSelectOption { + .euiContextMenuItem__text { + display: flex; + align-items: center; + + .euiToken { + margin-right: $euiSizeS; + } + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.test.tsx new file mode 100644 index 00000000000000..b313e16c0bda11 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.test.tsx @@ -0,0 +1,71 @@ +/* + * 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 { setMockActions } from '../../../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiSuperSelect } from '@elastic/eui'; + +import { SchemaTypes } from '../../../../../../shared/types'; + +import { Boosts } from './boosts'; + +describe('Boosts', () => { + const actions = { + addBoost: jest.fn(), + }; + + beforeAll(() => { + setMockActions(actions); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + const props = { + name: 'foo', + type: 'number' as SchemaTypes, + }; + + it('renders a select box that allows users to create boosts of various types', () => { + const wrapper = shallow(); + + const select = wrapper.find(EuiSuperSelect); + expect(select.prop('options').map((o: any) => o.value)).toEqual([ + 'add-boost', + 'functional', + 'proximity', + 'value', + ]); + }); + + it('will not render functional or proximity options if "type" prop is "text"', () => { + const wrapper = shallow( + + ); + + const select = wrapper.find(EuiSuperSelect); + expect(select.prop('options').map((o: any) => o.value)).toEqual(['add-boost', 'value']); + }); + + it('will add a boost of the selected type when a selection is made', () => { + const wrapper = shallow(); + + wrapper.find(EuiSuperSelect).simulate('change', 'functional'); + + expect(actions.addBoost).toHaveBeenCalledWith('foo', 'functional'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.tsx new file mode 100644 index 00000000000000..1ad27346d2630e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.tsx @@ -0,0 +1,118 @@ +/* + * 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 React, { useMemo } from 'react'; + +import { useActions } from 'kea'; + +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle, EuiSuperSelect } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { TEXT } from '../../../../../../shared/constants/field_types'; +import { SchemaTypes } from '../../../../../../shared/types'; + +import { BoostIcon } from '../../../boost_icon'; +import { FUNCTIONAL_DISPLAY, PROXIMITY_DISPLAY, VALUE_DISPLAY } from '../../../constants'; +import { RelevanceTuningLogic } from '../../../relevance_tuning_logic'; +import { Boost, BoostType } from '../../../types'; + +import { BoostItem } from './boost_item'; + +import './boosts.scss'; + +const BASE_OPTIONS = [ + { + value: 'add-boost', + inputDisplay: i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addBoostDropDownOptionLabel', + { + defaultMessage: 'Add boost', + } + ), + disabled: true, + }, + { + value: BoostType.Functional, + inputDisplay: ( + <> + + {FUNCTIONAL_DISPLAY} + + ), + }, + { + value: BoostType.Proximity, + inputDisplay: ( + <> + + {PROXIMITY_DISPLAY} + + ), + }, + { + value: BoostType.Value, + inputDisplay: ( + <> + + {VALUE_DISPLAY} + + ), + }, +]; + +const filterInvalidOptions = (value: BoostType, type: SchemaTypes) => { + // Proximity and Functional boost types are not valid for text fields + if (type === TEXT && [BoostType.Proximity, BoostType.Functional].includes(value)) return false; + return true; +}; + +interface Props { + name: string; + type: SchemaTypes; + boosts?: Boost[]; +} + +export const Boosts: React.FC = ({ name, type, boosts = [] }) => { + const { addBoost } = useActions(RelevanceTuningLogic); + + const selectOptions = useMemo( + () => BASE_OPTIONS.filter((option) => filterInvalidOptions(option.value as BoostType, type)), + [type] + ); + + return ( + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.title', + { + defaultMessage: 'Boosts', + } + )} +

+
+
+ + addBoost(name, value as BoostType)} + /> + +
+ {boosts.map((boost, index) => ( + + ))} +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.test.ts new file mode 100644 index 00000000000000..f6852569213a68 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.test.ts @@ -0,0 +1,73 @@ +/* + * 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 { Boost, BoostFunction, BoostType, BoostOperation } from '../../../types'; + +import { getBoostSummary } from './get_boost_summary'; + +describe('getBoostSummary', () => { + describe('when the boost type is "value"', () => { + const boost: Boost = { + type: BoostType.Value, + value: ['1', '2'], + factor: 5, + }; + + it('creates a summary that is the joined values', () => { + expect(getBoostSummary(boost)).toEqual('1,2'); + }); + + it('creates an empty summary if there is no value', () => { + expect( + getBoostSummary({ + ...boost, + value: undefined, + }) + ).toEqual(''); + }); + }); + + describe('when the boost type is "proximity"', () => { + const boost: Boost = { + type: BoostType.Proximity, + function: 'gaussian' as BoostFunction, + factor: 5, + }; + + it('creates a summary that is just the name of the function', () => { + expect(getBoostSummary(boost)).toEqual('gaussian'); + }); + + it('creates an empty summary if there is no function', () => { + expect( + getBoostSummary({ + ...boost, + function: undefined, + }) + ).toEqual(''); + }); + }); + + describe('when the boost type is "functional"', () => { + const boost: Boost = { + type: BoostType.Functional, + function: BoostFunction.Gaussian, + operation: BoostOperation.Add, + factor: 5, + }; + + it('creates a summary that is name of the function and operation', () => { + expect(getBoostSummary(boost)).toEqual('gaussian add'); + }); + + it('prints empty if function or operation is missing', () => { + expect(getBoostSummary({ ...boost, function: undefined })).toEqual(BoostOperation.Add); + expect(getBoostSummary({ ...boost, operation: undefined })).toEqual(BoostFunction.Gaussian); + expect(getBoostSummary({ ...boost, function: undefined, operation: undefined })).toEqual(''); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.ts new file mode 100644 index 00000000000000..f3922ebb0fffe9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Boost, BoostType } from '../../../types'; + +export const getBoostSummary = (boost: Boost): string => { + if (boost.type === BoostType.Value) { + return !boost.value ? '' : boost.value.join(','); + } else if (boost.type === BoostType.Proximity) { + return boost.function || ''; + } else { + return [boost.function || '', boost.operation || ''].join(' ').trim(); + } +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/index.ts new file mode 100644 index 00000000000000..dc269132769b81 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/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 { Boosts } from './boosts'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/index.ts new file mode 100644 index 00000000000000..dc5b95320d4db6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/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 { RelevanceTuningItemContent } from './relevance_tuning_item_content'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.scss new file mode 100644 index 00000000000000..63718a95551fa5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.scss @@ -0,0 +1,6 @@ +.relevanceTuningForm { + &__itemContent { + border: none; + border-top: $euiBorderThin; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx new file mode 100644 index 00000000000000..18a75766cd67be --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx @@ -0,0 +1,65 @@ +/* + * 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 React from 'react'; + +import { shallow } from 'enzyme'; + +import { SchemaTypes } from '../../../../../shared/types'; +import { BoostType } from '../../types'; + +import { RelevanceTuningItemContent } from './relevance_tuning_item_content'; +import { TextSearchToggle } from './text_search_toggle'; +import { WeightSlider } from './weight_slider'; + +describe('RelevanceTuningItemContent', () => { + const props = { + name: 'foo', + type: 'text' as SchemaTypes, + boosts: [ + { + factor: 2, + type: BoostType.Value, + }, + ], + field: { + weight: 1, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const wrapper = shallow(); + + const textSearchToggle = wrapper.find(TextSearchToggle); + expect(textSearchToggle.exists()).toBe(true); + expect(textSearchToggle.prop('name')).toBe(props.name); + expect(textSearchToggle.prop('type')).toBe(props.type); + expect(textSearchToggle.prop('field')).toBe(props.field); + + const weightSlider = wrapper.find(WeightSlider); + expect(weightSlider.exists()).toBe(true); + expect(weightSlider.prop('name')).toBe(props.name); + expect(weightSlider.prop('field')).toBe(props.field); + }); + + it('will not render a WeightSlider if the field prop is empty', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(WeightSlider).exists()).toBe(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx new file mode 100644 index 00000000000000..29ab559485d776 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx @@ -0,0 +1,39 @@ +/* + * 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 React from 'react'; + +import { EuiPanel } from '@elastic/eui'; + +import { SchemaTypes } from '../../../../../shared/types'; + +import { Boost, SearchField } from '../../types'; + +import { Boosts } from './boosts'; +import { TextSearchToggle } from './text_search_toggle'; +import { WeightSlider } from './weight_slider'; + +import './relevance_tuning_item_content.scss'; + +interface Props { + name: string; + type: SchemaTypes; + boosts?: Boost[]; + field?: SearchField; +} + +export const RelevanceTuningItemContent: React.FC = ({ name, type, boosts, field }) => { + return ( + <> + + + {field && } + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.test.tsx new file mode 100644 index 00000000000000..7225fce5daa611 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.test.tsx @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockActions } from '../../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiSwitch } from '@elastic/eui'; + +import { SchemaTypes } from '../../../../../shared/types'; + +import { TextSearchToggle } from './text_search_toggle'; + +describe('TextSearchToggle', () => { + const actions = { + toggleSearchField: jest.fn(), + }; + + beforeAll(() => { + setMockActions(actions); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('typical render', () => { + let wrapper: ShallowWrapper; + + const props = { + name: 'foo', + type: 'text' as SchemaTypes, + field: { + weight: 1, + }, + }; + + beforeAll(() => { + wrapper = shallow(); + }); + + it('renders a toggle button', () => { + expect(wrapper.find(EuiSwitch).exists()).toBe(true); + }); + + it('shows the toggle button as checked if any value was passed in the "field" prop', () => { + expect(wrapper.find(EuiSwitch).prop('checked')).toBe(true); + }); + + it('shows the toggle as enabled if "text" was passed in the "type" prop', () => { + expect(wrapper.find(EuiSwitch).prop('disabled')).toBe(false); + }); + + it('shows a relevant label if "text" was passed in the "type" prop', () => { + expect(wrapper.find(EuiSwitch).prop('label')).toBe('Search this field'); + }); + + it('will update toggled state when clicked', () => { + wrapper.find(EuiSwitch).simulate('change'); + expect(actions.toggleSearchField).toHaveBeenCalledWith('foo', true); + }); + }); + + describe('when a non-"text" type is passed in the "type" prop', () => { + let wrapper: ShallowWrapper; + + const props = { + name: 'foo', + type: 'number' as SchemaTypes, + field: { + weight: 1, + }, + }; + + beforeAll(() => { + wrapper = shallow(); + }); + + it('shows the toggle button as disabled', () => { + expect(wrapper.find(EuiSwitch).prop('checked')).toBe(true); + }); + + it('shows a relevant label', () => { + expect(wrapper.find(EuiSwitch).prop('label')).toBe( + 'Search can only be enabled on text fields' + ); + }); + + it('will not update state when the clicked', () => { + wrapper.find(EuiSwitch).simulate('change'); + expect(actions.toggleSearchField).not.toHaveBeenCalled(); + }); + }); + + describe('when no field prop is passed', () => { + let wrapper: ShallowWrapper; + + const props = { + name: 'foo', + type: 'text' as SchemaTypes, + }; + + beforeAll(() => { + wrapper = shallow(); + }); + + it('shows the toggle button as unchecked', () => { + expect(wrapper.find(EuiSwitch).prop('checked')).toBe(false); + }); + + it('will update toggled state when clicked', () => { + wrapper.find(EuiSwitch).simulate('change'); + expect(actions.toggleSearchField).toHaveBeenCalledWith('foo', false); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.tsx new file mode 100644 index 00000000000000..607ddd9c6b0782 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.tsx @@ -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 React from 'react'; + +import { useActions } from 'kea'; + +import { EuiFormRow, EuiSwitch } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { TEXT } from '../../../../../shared/constants/field_types'; +import { SchemaTypes } from '../../../../../shared/types'; + +import { RelevanceTuningLogic } from '../../relevance_tuning_logic'; +import { SearchField } from '../../types'; + +interface Props { + name: string; + type: SchemaTypes; + field?: SearchField; +} + +export const TextSearchToggle: React.FC = ({ name, type, field }) => { + const { toggleSearchField } = useActions(RelevanceTuningLogic); + + return ( + + type === TEXT && toggleSearchField(name, !!field)} + checked={!!field} + disabled={type !== TEXT} + /> + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.test.tsx new file mode 100644 index 00000000000000..21a112a4ea9889 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.test.tsx @@ -0,0 +1,50 @@ +/* + * 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 { setMockActions } from '../../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiRange } from '@elastic/eui'; + +import { WeightSlider } from './weight_slider'; + +describe('WeightSlider', () => { + let wrapper: ShallowWrapper; + + const actions = { + updateFieldWeight: jest.fn(), + }; + + beforeAll(() => { + setMockActions(actions); + wrapper = shallow( + + ); + }); + + it('renders with an initial value set', () => { + expect(wrapper.find(EuiRange).exists()).toBe(true); + expect(wrapper.find(EuiRange).prop('value')).toBe(2.2); + }); + + it('updates field weight in state when the value changes', () => { + wrapper.find(EuiRange).simulate('change', { + target: { + value: '1.3', + }, + }); + expect(actions.updateFieldWeight).toHaveBeenCalledWith('foo', 1.3); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.tsx new file mode 100644 index 00000000000000..02e83b81b2cb13 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.tsx @@ -0,0 +1,53 @@ +/* + * 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 React from 'react'; + +import { useActions } from 'kea'; + +import { EuiFormRow, EuiRange } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { RelevanceTuningLogic } from '../../relevance_tuning_logic'; +import { SearchField } from '../../types'; + +interface Props { + name: string; + field: SearchField; +} + +export const WeightSlider: React.FC = ({ name, field }) => { + const { updateFieldWeight } = useActions(RelevanceTuningLogic); + + return ( + + + updateFieldWeight( + name, + parseFloat((e as React.ChangeEvent).target.value) + ) + } + showInput + compressed + fullWidth + /> + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.scss new file mode 100644 index 00000000000000..853edd3fed0a38 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.scss @@ -0,0 +1,23 @@ +.relevanceTuningForm { + .valueBadge { + display: inline-flex; + align-items: center; + height: $euiSizeL; + border: 1px solid $euiColorLightShade; + border-radius: $euiSizeXS; + // To match the background of EuiToken, for which there is no direct variable to + // reference + background: tintOrShade($euiColorVis1, 90%, 70%); + color: $euiColorVis1; + padding: 0 $euiSizeS; + + .euiIcon { + margin-right: $euiSizeXS; + } + } + + .valueBadge--disabled { + background: transparent; + color: $euiColorMediumShade; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.tsx new file mode 100644 index 00000000000000..8397087ca69b46 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.tsx @@ -0,0 +1,22 @@ +/* + * 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 React from 'react'; + +import classNames from 'classnames'; + +import './value_badge.scss'; + +export const ValueBadge: React.FC<{ children: React.ReactNode; disabled?: boolean }> = ({ + children, + disabled = false, +}) => { + const className = classNames('valueBadge', { + 'valueBadge--disabled': disabled, + }); + return {children}; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index 194848bcfc86c0..a7ee6f9755fc4a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -9,7 +9,7 @@ import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../ import { nextTick } from '@kbn/test/jest'; -import { Boost, BoostType } from './types'; +import { Boost, BoostFunction, BoostOperation, BoostType } from './types'; import { RelevanceTuningLogic } from './'; @@ -24,7 +24,7 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, }, ], @@ -97,6 +97,18 @@ describe('RelevanceTuningLogic', () => { schemaConflicts, }); }); + + it('should default schemaConflicts if it is not passed', () => { + mount({ + dataLoading: true, + }); + RelevanceTuningLogic.actions.onInitializeRelevanceTuning({ + searchSettings, + schema, + }); + + expect(RelevanceTuningLogic.values.schemaConflicts).toEqual({}); + }); }); describe('setSearchSettings', () => { @@ -237,7 +249,7 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 1, - type: 'functional', + type: BoostType.Functional, }, boost, ], @@ -265,7 +277,7 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, value: 5, }, @@ -289,7 +301,7 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, value: ['5'], }, @@ -324,19 +336,25 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, newBoost: true, // This should be deleted before sent to the server }, ], }, + search_fields: { + bar: { + weight: 1, + }, + }, }; const searchSettingsWithoutNewBoostProp = { + ...searchSettingsWithNewBoostProp, boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, }, ], @@ -475,7 +493,7 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, newBoost: true, // This should be deleted before sent to the server }, @@ -487,7 +505,7 @@ describe('RelevanceTuningLogic', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, }, ], @@ -672,7 +690,7 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 2, - type: 'value', + type: BoostType.Value, }, ], }, @@ -680,7 +698,7 @@ describe('RelevanceTuningLogic', () => { }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - RelevanceTuningLogic.actions.addBoost('foo', 'functional'); + RelevanceTuningLogic.actions.addBoost('foo', BoostType.Functional); expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ ...searchSettings, @@ -688,12 +706,12 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 2, - type: 'value', + type: BoostType.Value, }, { factor: 1, newBoost: true, - type: 'functional', + type: BoostType.Functional, }, ], }, @@ -709,7 +727,7 @@ describe('RelevanceTuningLogic', () => { }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - RelevanceTuningLogic.actions.addBoost('foo', 'functional'); + RelevanceTuningLogic.actions.addBoost('foo', BoostType.Functional); expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ ...searchSettings, @@ -718,7 +736,7 @@ describe('RelevanceTuningLogic', () => { { factor: 1, newBoost: true, - type: 'functional', + type: BoostType.Functional, }, ], }, @@ -735,11 +753,11 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 1, - type: 'functional', + type: BoostType.Functional, }, { factor: 2, - type: 'value', + type: BoostType.Value, }, ], }, @@ -756,7 +774,7 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 1, - type: 'functional', + type: BoostType.Functional, }, ], }, @@ -771,7 +789,7 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 1, - type: 'functional', + type: BoostType.Functional, }, ], }, @@ -796,7 +814,7 @@ describe('RelevanceTuningLogic', () => { foo: [ { factor: 1, - type: 'functional', + type: BoostType.Functional, }, ], }, @@ -816,7 +834,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); @@ -826,7 +844,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 5, - type: 'functional', + type: BoostType.Functional, }) ); }); @@ -835,7 +853,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); @@ -845,7 +863,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 5.3, - type: 'functional', + type: BoostType.Functional, }) ); }); @@ -856,7 +874,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', 'b', 'c'], }), }); @@ -867,7 +885,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', 'a', 'c'], }) ); @@ -877,7 +895,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); @@ -887,7 +905,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a'], }) ); @@ -902,7 +920,7 @@ describe('RelevanceTuningLogic', () => { }, searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'proximity', + type: BoostType.Proximity, center: 1, }), }); @@ -913,7 +931,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'proximity', + type: BoostType.Proximity, center: 4, }) ); @@ -925,7 +943,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a'], }), }); @@ -936,7 +954,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', ''], }) ); @@ -946,7 +964,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); @@ -956,7 +974,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['', ''], }) ); @@ -966,7 +984,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', ''], }), }); @@ -977,7 +995,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', ''], }) ); @@ -989,7 +1007,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', 'b', 'c'], }), }); @@ -1000,7 +1018,7 @@ describe('RelevanceTuningLogic', () => { expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, value: ['a', 'c'], }) ); @@ -1010,7 +1028,7 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); @@ -1026,18 +1044,23 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - RelevanceTuningLogic.actions.updateBoostSelectOption('foo', 1, 'function', 'exponential'); + RelevanceTuningLogic.actions.updateBoostSelectOption( + 'foo', + 1, + 'function', + BoostFunction.Exponential + ); expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', - function: 'exponential', + type: BoostType.Functional, + function: BoostFunction.Exponential, }) ); }); @@ -1046,18 +1069,23 @@ describe('RelevanceTuningLogic', () => { mount({ searchSettings: searchSettingsWithBoost({ factor: 1, - type: 'functional', + type: BoostType.Functional, }), }); jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - RelevanceTuningLogic.actions.updateBoostSelectOption('foo', 1, 'operation', 'add'); + RelevanceTuningLogic.actions.updateBoostSelectOption( + 'foo', + 1, + 'operation', + BoostOperation.Add + ); expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, - type: 'functional', - operation: 'add', + type: BoostType.Functional, + operation: BoostOperation.Add, }) ); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts index cd3d8b5686cc05..d567afee9d0627 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts @@ -21,7 +21,14 @@ import { DELETE_SUCCESS_MESSAGE, DELETE_CONFIRMATION_MESSAGE, } from './constants'; -import { BaseBoost, Boost, BoostType, SearchSettings } from './types'; +import { + BaseBoost, + Boost, + BoostFunction, + BoostOperation, + BoostType, + SearchSettings, +} from './types'; import { filterIfTerm, parseBoostCenter, @@ -32,7 +39,7 @@ import { interface RelevanceTuningProps { searchSettings: SearchSettings; schema: Schema; - schemaConflicts: SchemaConflicts; + schemaConflicts?: SchemaConflicts; } interface RelevanceTuningActions { @@ -82,7 +89,7 @@ interface RelevanceTuningActions { name: string, boostIndex: number, optionType: keyof BaseBoost, - value: string + value: BoostOperation | BoostFunction ): { name: string; boostIndex: number; @@ -176,7 +183,7 @@ export const RelevanceTuningLogic = kea< schemaConflicts: [ {}, { - onInitializeRelevanceTuning: (_, { schemaConflicts }) => schemaConflicts, + onInitializeRelevanceTuning: (_, { schemaConflicts }) => schemaConflicts || {}, }, ], showSchemaConflictCallout: [ @@ -497,7 +504,11 @@ export const RelevanceTuningLogic = kea< const { searchSettings } = values; const { boosts } = searchSettings; const updatedBoosts = cloneDeep(boosts[name]); - updatedBoosts[boostIndex][optionType] = value; + if (optionType === 'operation') { + updatedBoosts[boostIndex][optionType] = value as BoostOperation; + } else { + updatedBoosts[boostIndex][optionType] = value as BoostFunction; + } actions.setSearchSettings({ ...searchSettings, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts index a1ed9797b9f5a5..95bd33aac5b9ff 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts @@ -5,11 +5,26 @@ * 2.0. */ -export type BoostType = 'value' | 'functional' | 'proximity'; +export enum BoostType { + Value = 'value', + Functional = 'functional', + Proximity = 'proximity', +} + +export enum BoostFunction { + Gaussian = 'gaussian', + Exponential = 'exponential', + Linear = 'linear', +} + +export enum BoostOperation { + Add = 'add', + Multiple = 'multiply', +} export interface BaseBoost { - operation?: string; - function?: string; + operation?: BoostOperation; + function?: BoostFunction; } // A boost that comes from the server, before we normalize it has a much looser schema @@ -25,13 +40,13 @@ export interface RawBoost extends BaseBoost { export interface Boost extends RawBoost { value?: string[]; } + +export interface SearchField { + weight: number; +} + export interface SearchSettings { boosts: Record; - search_fields: Record< - string, - { - weight: number; - } - >; + search_fields: Record; result_fields?: object; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts index a6598bf991c13e..1694015ed68615 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts @@ -39,7 +39,7 @@ describe('removeBoostStateProps', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, newBoost: true, }, @@ -56,7 +56,7 @@ describe('removeBoostStateProps', () => { boosts: { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 5, }, ], @@ -66,39 +66,46 @@ describe('removeBoostStateProps', () => { }); describe('parseBoostCenter', () => { - it('should parse a boost center', () => { - expect(parseBoostCenter('text', 5)).toEqual(5); - expect(parseBoostCenter('text', '4')).toEqual('4'); + it('should parse the value to a number when the type is number', () => { expect(parseBoostCenter('number', 5)).toEqual(5); expect(parseBoostCenter('number', '5')).toEqual(5); }); + + it('should not try to parse the value when the type is text', () => { + expect(parseBoostCenter('text', 5)).toEqual(5); + expect(parseBoostCenter('text', '4')).toEqual('4'); + }); + + it('should leave text invalid numbers alone', () => { + expect(parseBoostCenter('number', 'foo')).toEqual('foo'); + }); }); describe('normalizeBoostValues', () => { const boosts = { foo: [ { - type: 'value' as BoostType, + type: BoostType.Value, factor: 9.5, value: 1, }, { - type: 'value' as BoostType, + type: BoostType.Value, factor: 9.5, value: '1', }, { - type: 'value' as BoostType, + type: BoostType.Value, factor: 9.5, value: [1], }, { - type: 'value' as BoostType, + type: BoostType.Value, factor: 9.5, value: ['1'], }, { - type: 'value' as BoostType, + type: BoostType.Value, factor: 9.5, value: [ '1', @@ -115,13 +122,13 @@ describe('normalizeBoostValues', () => { ], bar: [ { - type: 'proximity' as BoostType, + type: BoostType.Proximity, factor: 9.5, }, ], sp_def: [ { - type: 'functional' as BoostType, + type: BoostType.Functional, factor: 5, }, ], @@ -129,19 +136,19 @@ describe('normalizeBoostValues', () => { it('converts all value types to string for consistency', () => { expect(normalizeBoostValues(boosts)).toEqual({ - bar: [{ factor: 9.5, type: 'proximity' }], + bar: [{ factor: 9.5, type: BoostType.Proximity }], foo: [ - { factor: 9.5, type: 'value', value: ['1'] }, - { factor: 9.5, type: 'value', value: ['1'] }, - { factor: 9.5, type: 'value', value: ['1'] }, - { factor: 9.5, type: 'value', value: ['1'] }, + { factor: 9.5, type: BoostType.Value, value: ['1'] }, + { factor: 9.5, type: BoostType.Value, value: ['1'] }, + { factor: 9.5, type: BoostType.Value, value: ['1'] }, + { factor: 9.5, type: BoostType.Value, value: ['1'] }, { factor: 9.5, - type: 'value', + type: BoostType.Value, value: ['1', '1', '2', '2', 'true', '[object Object]', '[object Object]'], }, ], - sp_def: [{ type: 'functional', factor: 5 }], + sp_def: [{ type: BoostType.Functional, factor: 5 }], }); }); }); From a558920176abdf5e41208a0b6f728b46c09efe19 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Thu, 18 Feb 2021 08:47:03 -0500 Subject: [PATCH 35/93] [Monitoring] Fetch status once and change fetchStatus to support an array of clusters (#91749) * Fetch status once and change fetchStatus to support an array of clusters * Update test --- .../server/lib/alerts/fetch_status.test.ts | 49 +++++++------------ .../server/lib/alerts/fetch_status.ts | 4 +- .../lib/cluster/get_clusters_from_request.js | 27 +++++++--- .../server/routes/api/v1/alerts/status.ts | 2 +- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts index e30d2ba1044fb3..0d2d9fdbed6358 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts @@ -74,12 +74,9 @@ describe('fetchStatus', () => { }); it('should fetch from the alerts client', async () => { - const status = await fetchStatus( - alertsClient as any, - licenseService as any, - alertTypes, - defaultClusterState.clusterUuid - ); + const status = await fetchStatus(alertsClient as any, licenseService as any, alertTypes, [ + defaultClusterState.clusterUuid, + ]); expect(status).toEqual({ monitoring_alert_cpu_usage: { rawAlert: { id: 1 }, @@ -99,24 +96,18 @@ describe('fetchStatus', () => { }, ]; - const status = await fetchStatus( - alertsClient as any, - licenseService as any, - alertTypes, - defaultClusterState.clusterUuid - ); + const status = await fetchStatus(alertsClient as any, licenseService as any, alertTypes, [ + defaultClusterState.clusterUuid, + ]); expect(Object.values(status).length).toBe(1); expect(Object.keys(status)).toEqual(alertTypes); expect(status[alertType].states[0].state.ui.isFiring).toBe(true); }); it('should pass in the right filter to the alerts client', async () => { - await fetchStatus( - alertsClient as any, - licenseService as any, - alertTypes, - defaultClusterState.clusterUuid - ); + await fetchStatus(alertsClient as any, licenseService as any, alertTypes, [ + defaultClusterState.clusterUuid, + ]); expect((alertsClient.find as jest.Mock).mock.calls[0][0].options.filter).toBe( `alert.attributes.alertTypeId:${alertType}` ); @@ -127,12 +118,9 @@ describe('fetchStatus', () => { alertTypeState: null, })) as any; - const status = await fetchStatus( - alertsClient as any, - licenseService as any, - alertTypes, - defaultClusterState.clusterUuid - ); + const status = await fetchStatus(alertsClient as any, licenseService as any, alertTypes, [ + defaultClusterState.clusterUuid, + ]); expect(status[alertType].states.length).toEqual(0); }); @@ -142,12 +130,9 @@ describe('fetchStatus', () => { data: [], })) as any; - const status = await fetchStatus( - alertsClient as any, - licenseService as any, - alertTypes, - defaultClusterState.clusterUuid - ); + const status = await fetchStatus(alertsClient as any, licenseService as any, alertTypes, [ + defaultClusterState.clusterUuid, + ]); expect(status).toEqual({}); }); @@ -163,7 +148,7 @@ describe('fetchStatus', () => { alertsClient as any, customLicenseService as any, [ALERT_CLUSTER_HEALTH], - defaultClusterState.clusterUuid + [defaultClusterState.clusterUuid] ); expect(customLicenseService.getWatcherFeature).toHaveBeenCalled(); }); @@ -200,7 +185,7 @@ describe('fetchStatus', () => { customAlertsClient as any, licenseService as any, [ALERT_CPU_USAGE, ALERT_DISK_USAGE, ALERT_MISSING_MONITORING_DATA], - defaultClusterState.clusterUuid + [defaultClusterState.clusterUuid] ); expect(Object.keys(status)).toEqual([ ALERT_CPU_USAGE, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts index 399b26a6c5c314..3ccb4d3a9c4d5d 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts @@ -20,7 +20,7 @@ export async function fetchStatus( alertsClient: AlertsClient, licenseService: MonitoringLicenseService, alertTypes: string[] | undefined, - clusterUuid: string, + clusterUuids: string[], filters: CommonAlertFilter[] = [] ): Promise<{ [type: string]: CommonAlertStatus }> { const types: Array<{ type: string; result: CommonAlertStatus }> = []; @@ -57,7 +57,7 @@ export async function fetchStatus( } for (const state of alertInstanceState.alertStates) { const meta = instance.meta; - if (clusterUuid && state.cluster.clusterUuid !== clusterUuid) { + if (clusterUuids && !clusterUuids.includes(state.cluster.clusterUuid)) { return accum; } diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js index 47e3cef0674110..0bed25a70d0480 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js @@ -120,6 +120,13 @@ export async function getClustersFromRequest( // add alerts data if (isInCodePath(codePaths, [CODE_PATH_ALERTS])) { const alertsClient = req.getAlertsClient(); + const alertStatus = await fetchStatus( + alertsClient, + req.server.plugins.monitoring.info, + undefined, + clusters.map((cluster) => cluster.cluster_uuid) + ); + for (const cluster of clusters) { const verification = verifyMonitoringLicense(req.server); if (!verification.enabled) { @@ -154,12 +161,20 @@ export async function getClustersFromRequest( if (prodLicenseInfo.clusterAlerts.enabled) { try { cluster.alerts = { - list: await fetchStatus( - alertsClient, - req.server.plugins.monitoring.info, - undefined, - cluster.cluster_uuid - ), + list: Object.keys(alertStatus).reduce((accum, alertName) => { + const value = alertStatus[alertName]; + if (value.states && value.states.length) { + accum[alertName] = { + ...value, + states: value.states.filter( + (state) => state.state.cluster.clusterUuid === cluster.cluster_uuid + ), + }; + } else { + accum[alertName] = value; + } + return accum; + }, {}), alertsMeta: { enabled: true, }, diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts index d0a4de7b5b3783..95e2cb63bec86d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts @@ -43,7 +43,7 @@ export function alertStatusRoute(server: any, npRoute: RouteDependencies) { alertsClient, npRoute.licenseService, alertTypeIds, - clusterUuid, + [clusterUuid], filters as CommonAlertFilter[] ); return response.ok({ body: status }); From 7a7f071236fb1884dab9cfa861cbfecd2894fecc Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Thu, 18 Feb 2021 14:51:14 +0100 Subject: [PATCH 36/93] [ILM] Update Delete phase default days (#91811) * update delete phase default * added test for delete phase serialization --- .../edit_policy/edit_policy.test.ts | 23 +++++++++++++++++++ .../sections/edit_policy/form/schema.ts | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index f1a15d805faf8a..859b4adce50285 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -479,6 +479,29 @@ describe('', () => { component.update(); }); + test('serialization', async () => { + httpRequestsMockHelpers.setLoadPolicies([DEFAULT_POLICY]); + await act(async () => { + testBed = await setup(); + }); + const { component, actions } = testBed; + component.update(); + await actions.delete.enablePhase(); + await actions.setWaitForSnapshotPolicy('test'); + await actions.savePolicy(); + const latestRequest = server.requests[server.requests.length - 1]; + const entirePolicy = JSON.parse(JSON.parse(latestRequest.requestBody).body); + expect(entirePolicy.phases.delete).toEqual({ + min_age: '365d', + actions: { + delete: {}, + wait_for_snapshot: { + policy: 'test', + }, + }, + }); + }); + test('wait for snapshot policy field should correctly display snapshot policy name', () => { expect(testBed.find('snapshotPolicyCombobox').prop('data-currentvalue')).toEqual([ { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts index 600a660657863c..65fc82b7ccc685 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts @@ -354,7 +354,7 @@ export const schema: FormSchema = { }, delete: { min_age: { - defaultValue: '0', + defaultValue: '365', validations: [ { validator: minAgeValidator, From dadf0e65649db6310a85e8911532f3241fb22b4e Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Thu, 18 Feb 2021 07:57:19 -0600 Subject: [PATCH 37/93] Pass service node name in query for instance table links (#91796) For a non-Java service, the previous link was like: ``` http://localhost:5601/kbn/app/apm/services/opbeans-python/metrics?rangeFrom=now-15m&rangeTo=now ``` which did not filter by the `service.node.name`. It now is: ``` http://localhost:5601/kbn/app/apm/services/opbeans-python/metrics?kuery=service.node.name:%226a7f116fe344aee7e92fceeb426cbfdf6a534a8e3ba6345c16a47793eba6daf5%22&rangeFrom=now-15m&rangeTo=now ```` Which links to the metrics page with the filter applied. The component is using a `MetricOverviewLink` which was using a `EuiLink` and passing throught the props, including `mergeQuery`, which includes the `kuery` parameter. Replace the `EuiLink` with an `APMLink` which does use the `mergeQuery` prop and does pass the parameters through correctly. Looks like this was changed to an `EuiLink` by a refactor in #86986. Since we'll be making some further changes to how `kuery` is handled in #84526, I'm just making the minimal change to fix this bug at this time. --- .../components/shared/Links/apm/MetricOverviewLink.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx index 3bfdb5df61c2ee..c3d418b63426b5 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx @@ -5,10 +5,9 @@ * 2.0. */ -import { EuiLink } from '@elastic/eui'; import React from 'react'; import { APMQueryParams } from '../url_helpers'; -import { APMLinkExtendProps, useAPMHref } from './APMLink'; +import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; const persistedFilters: Array = [ 'host', @@ -29,6 +28,5 @@ interface Props extends APMLinkExtendProps { } export function MetricOverviewLink({ serviceName, ...rest }: Props) { - const href = useMetricOverviewHref(serviceName); - return ; + return ; } From 7503fd256a1726c85bd75b228045707ee5a3bbf2 Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Thu, 18 Feb 2021 15:12:41 +0100 Subject: [PATCH 38/93] support serializing nested searchsource (#91525) --- ...gins-data-public.searchsource.getfields.md | 9 +- ...plugin-plugins-data-public.searchsource.md | 2 +- ...-plugins-data-public.searchsourcefields.md | 1 + ...s-data-public.searchsourcefields.parent.md | 11 + .../search_source/search_source.test.ts | 228 ++++++------------ .../search/search_source/search_source.ts | 60 +---- .../data/common/search/search_source/types.ts | 2 + src/plugins/data/public/public.api.md | 4 +- 8 files changed, 101 insertions(+), 216 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.parent.md diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md index b0ccedb819c95d..856e43588ffb78 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md @@ -9,15 +9,8 @@ returns all search source fields Signature: ```typescript -getFields(recurse?: boolean): SearchSourceFields; +getFields(): SearchSourceFields; ``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| recurse | boolean | | - Returns: `SearchSourceFields` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md index 3250561c8b82e9..b2382d35f7d768 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md @@ -35,7 +35,7 @@ export declare class SearchSource | [fetch(options)](./kibana-plugin-plugins-data-public.searchsource.fetch.md) | | Fetch this source and reject the returned Promise on error | | [fetch$(options)](./kibana-plugin-plugins-data-public.searchsource.fetch_.md) | | Fetch this source from Elasticsearch, returning an observable over the response(s) | | [getField(field, recurse)](./kibana-plugin-plugins-data-public.searchsource.getfield.md) | | Gets a single field from the fields | -| [getFields(recurse)](./kibana-plugin-plugins-data-public.searchsource.getfields.md) | | returns all search source fields | +| [getFields()](./kibana-plugin-plugins-data-public.searchsource.getfields.md) | | returns all search source fields | | [getId()](./kibana-plugin-plugins-data-public.searchsource.getid.md) | | returns search source id | | [getOwnField(field)](./kibana-plugin-plugins-data-public.searchsource.getownfield.md) | | Get the field from our own fields, don't traverse up the chain | | [getParent()](./kibana-plugin-plugins-data-public.searchsource.getparent.md) | | Get the parent of this SearchSource {undefined\|searchSource} | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md index 683a35fabf5710..1d4547bb21d103 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md @@ -24,6 +24,7 @@ export interface SearchSourceFields | [highlight](./kibana-plugin-plugins-data-public.searchsourcefields.highlight.md) | any | | | [highlightAll](./kibana-plugin-plugins-data-public.searchsourcefields.highlightall.md) | boolean | | | [index](./kibana-plugin-plugins-data-public.searchsourcefields.index.md) | IndexPattern | | +| [parent](./kibana-plugin-plugins-data-public.searchsourcefields.parent.md) | SearchSourceFields | | | [query](./kibana-plugin-plugins-data-public.searchsourcefields.query.md) | Query | [Query](./kibana-plugin-plugins-data-public.query.md) | | [searchAfter](./kibana-plugin-plugins-data-public.searchsourcefields.searchafter.md) | EsQuerySearchAfter | | | [size](./kibana-plugin-plugins-data-public.searchsourcefields.size.md) | number | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.parent.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.parent.md new file mode 100644 index 00000000000000..3adb34a50ff9eb --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.parent.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) > [parent](./kibana-plugin-plugins-data-public.searchsourcefields.parent.md) + +## SearchSourceFields.parent property + +Signature: + +```typescript +parent?: SearchSourceFields; +``` diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 23ad7af14b093f..030e620bea34b4 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -95,164 +95,6 @@ describe('SearchSource', () => { } `); }); - - test('recurses parents to get the entire filters: plain object filter', () => { - const RECURSE = true; - - const parent = new SearchSource({}, searchSourceDependencies); - parent.setField('filter', [ - { - meta: { - index: 'd180cae0-60c3-11eb-8569-bd1f5ed24bc9', - params: {}, - alias: null, - disabled: false, - negate: false, - }, - query: { - range: { - '@date': { - gte: '2016-01-27T18:11:05.010Z', - lte: '2021-01-27T18:11:05.010Z', - format: 'strict_date_optional_time', - }, - }, - }, - }, - ]); - searchSource.setParent(parent); - searchSource.setField('aggs', 5); - expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` - Object { - "aggs": 5, - "filter": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", - "negate": false, - "params": Object {}, - }, - "query": Object { - "range": Object { - "@date": Object { - "format": "strict_date_optional_time", - "gte": "2016-01-27T18:11:05.010Z", - "lte": "2021-01-27T18:11:05.010Z", - }, - }, - }, - }, - ], - } - `); - - // calling twice gives the same result: no searchSources in the hierarchy were modified - expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` - Object { - "aggs": 5, - "filter": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", - "negate": false, - "params": Object {}, - }, - "query": Object { - "range": Object { - "@date": Object { - "format": "strict_date_optional_time", - "gte": "2016-01-27T18:11:05.010Z", - "lte": "2021-01-27T18:11:05.010Z", - }, - }, - }, - }, - ], - } - `); - }); - - test('recurses parents to get the entire filters: function filter', () => { - const RECURSE = true; - - const parent = new SearchSource({}, searchSourceDependencies); - parent.setField('filter', () => ({ - meta: { - index: 'd180cae0-60c3-11eb-8569-bd1f5ed24bc9', - params: {}, - alias: null, - disabled: false, - negate: false, - }, - query: { - range: { - '@date': { - gte: '2016-01-27T18:11:05.010Z', - lte: '2021-01-27T18:11:05.010Z', - format: 'strict_date_optional_time', - }, - }, - }, - })); - searchSource.setParent(parent); - searchSource.setField('aggs', 5); - expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` - Object { - "aggs": 5, - "filter": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", - "negate": false, - "params": Object {}, - }, - "query": Object { - "range": Object { - "@date": Object { - "format": "strict_date_optional_time", - "gte": "2016-01-27T18:11:05.010Z", - "lte": "2021-01-27T18:11:05.010Z", - }, - }, - }, - }, - ], - } - `); - - // calling twice gives the same result: no double-added filters - expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` - Object { - "aggs": 5, - "filter": Array [ - Object { - "meta": Object { - "alias": null, - "disabled": false, - "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", - "negate": false, - "params": Object {}, - }, - "query": Object { - "range": Object { - "@date": Object { - "format": "strict_date_optional_time", - "gte": "2016-01-27T18:11:05.010Z", - "lte": "2021-01-27T18:11:05.010Z", - }, - }, - }, - }, - ], - } - `); - }); }); describe('#removeField()', () => { @@ -975,4 +817,74 @@ describe('SearchSource', () => { expect(request._source).toEqual(['geometry']); }); }); + + describe('getSerializedFields', () => { + const filter = [ + { + query: 'query', + meta: { + alias: 'alias', + disabled: false, + negate: false, + index: '456', + }, + }, + ]; + + test('should return serialized fields', () => { + const indexPattern123 = { id: '123' } as IndexPattern; + searchSource.setField('index', indexPattern123); + searchSource.setField('filter', () => { + return filter; + }); + const serializedFields = searchSource.getSerializedFields(); + expect(serializedFields).toMatchInlineSnapshot( + { index: '123', filter }, + ` + Object { + "filter": Array [ + Object { + "meta": Object { + "alias": "alias", + "disabled": false, + "index": "456", + "negate": false, + }, + "query": "query", + }, + ], + "index": "123", + } + ` + ); + }); + + test('should support nested search sources', () => { + const indexPattern123 = { id: '123' } as IndexPattern; + searchSource.setField('index', indexPattern123); + searchSource.setField('from', 123); + const childSearchSource = searchSource.createChild(); + childSearchSource.setField('timeout', '100'); + const serializedFields = childSearchSource.getSerializedFields(true); + expect(serializedFields).toMatchInlineSnapshot( + { + timeout: '100', + parent: { + index: '123', + from: 123, + }, + }, + ` + Object { + "index": undefined, + "parent": Object { + "from": 123, + "index": "123", + }, + "timeout": "100", + } + ` + ); + }); + }); }); diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 8406c4900bef74..118bb04c1742b4 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -59,7 +59,7 @@ */ import { setWith } from '@elastic/safer-lodash-set'; -import { uniqueId, keyBy, pick, difference, omit, isFunction, isEqual, uniqWith } from 'lodash'; +import { uniqueId, keyBy, pick, difference, isFunction, isEqual, uniqWith } from 'lodash'; import { map, switchMap, tap } from 'rxjs/operators'; import { defer, from } from 'rxjs'; import { isObject } from 'rxjs/internal-compatibility'; @@ -114,8 +114,13 @@ export class SearchSource { private readonly dependencies: SearchSourceDependencies; constructor(fields: SearchSourceFields = {}, dependencies: SearchSourceDependencies) { - this.fields = fields; + const { parent, ...currentFields } = fields; + this.fields = currentFields; this.dependencies = dependencies; + + if (parent) { + this.setParent(new SearchSource(parent, dependencies)); + } } /** *** @@ -173,49 +178,7 @@ export class SearchSource { /** * returns all search source fields */ - getFields(recurse = false): SearchSourceFields { - let thisFilter = this.fields.filter; // type is single value, array, or function - if (thisFilter) { - if (typeof thisFilter === 'function') { - thisFilter = thisFilter() || []; // type is single value or array - } - - if (Array.isArray(thisFilter)) { - thisFilter = [...thisFilter]; - } else { - thisFilter = [thisFilter]; - } - } else { - thisFilter = []; - } - - if (recurse) { - const parent = this.getParent(); - if (parent) { - const parentFields = parent.getFields(recurse); - - let parentFilter = parentFields.filter; // type is single value, array, or function - if (parentFilter) { - if (typeof parentFilter === 'function') { - parentFilter = parentFilter() || []; // type is single value or array - } - - if (Array.isArray(parentFilter)) { - thisFilter.push(...parentFilter); - } else { - thisFilter.push(parentFilter); - } - } - - // add combined filters to the fields - const thisFields = { - ...this.fields, - filter: thisFilter, - }; - - return { ...parentFields, ...thisFields }; - } - } + getFields(): SearchSourceFields { return { ...this.fields }; } @@ -727,9 +690,7 @@ export class SearchSource { * serializes search source fields (which can later be passed to {@link ISearchStartSearchSource}) */ public getSerializedFields(recurse = false) { - const { filter: originalFilters, ...searchSourceFields } = omit(this.getFields(recurse), [ - 'size', - ]); + const { filter: originalFilters, size: omit, ...searchSourceFields } = this.getFields(); let serializedSearchSourceFields: SearchSourceFields = { ...searchSourceFields, index: (searchSourceFields.index ? searchSourceFields.index.id : undefined) as any, @@ -741,6 +702,9 @@ export class SearchSource { filter: filters, }; } + if (recurse && this.getParent()) { + serializedSearchSourceFields.parent = this.getParent()!.getSerializedFields(recurse); + } return serializedSearchSourceFields; } diff --git a/src/plugins/data/common/search/search_source/types.ts b/src/plugins/data/common/search/search_source/types.ts index 61d7165393b099..d06f521640da04 100644 --- a/src/plugins/data/common/search/search_source/types.ts +++ b/src/plugins/data/common/search/search_source/types.ts @@ -99,6 +99,8 @@ export interface SearchSourceFields { searchAfter?: EsQuerySearchAfter; timeout?: string; terminate_after?: number; + + parent?: SearchSourceFields; } export interface SearchSourceOptions { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 745f4a7d29d224..67423295dfe5e3 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2382,7 +2382,7 @@ export class SearchSource { // @deprecated fetch(options?: ISearchOptions): Promise>; getField(field: K, recurse?: boolean): SearchSourceFields[K]; - getFields(recurse?: boolean): SearchSourceFields; + getFields(): SearchSourceFields; getId(): string; getOwnField(field: K): SearchSourceFields[K]; getParent(): SearchSource | undefined; @@ -2428,6 +2428,8 @@ export interface SearchSourceFields { // (undocumented) index?: IndexPattern; // (undocumented) + parent?: SearchSourceFields; + // (undocumented) query?: Query; // Warning: (ae-forgotten-export) The symbol "EsQuerySearchAfter" needs to be exported by the entry point index.d.ts // From 3c0d73af8ba1b779125ff9512458a7e5094af38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Thu, 18 Feb 2021 14:35:27 +0000 Subject: [PATCH 39/93] [DOCS] Accept core changes (#91826) --- ...plugin-core-server.savedobjectsfindresult.sort.md | 2 +- ....savedobjectsrepository.openpointintimefortype.md | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md index 3cc02c404c8d71..17f52687243321 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.sort.md @@ -25,7 +25,7 @@ const page1 = await savedObjectsClient.find({ type: 'visualization', sortField: 'updated_at', sortOrder: 'asc', - pit, + pit: { id }, }); const lastHit = page1.saved_objects[page1.saved_objects.length - 1]; const page2 = await savedObjectsClient.find({ diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md index 63956ebee68f7b..6b668824845202 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md @@ -29,19 +29,16 @@ openPointInTimeForType(type: string | string[], { keepAlive, preference }?: Save ```ts -const repository = coreStart.savedObjects.createInternalRepository(); - -const { id } = await repository.openPointInTimeForType( - type: 'index-pattern', - { keepAlive: '2m' }, +const { id } = await savedObjectsClient.openPointInTimeForType( + type: 'visualization', + { keepAlive: '5m' }, ); const page1 = await savedObjectsClient.find({ type: 'visualization', sortField: 'updated_at', sortOrder: 'asc', - pit, + pit: { id, keepAlive: '2m' }, }); - const lastHit = page1.saved_objects[page1.saved_objects.length - 1]; const page2 = await savedObjectsClient.find({ type: 'visualization', @@ -50,7 +47,6 @@ const page2 = await savedObjectsClient.find({ pit: { id: page1.pit_id }, searchAfter: lastHit.sort, }); - await savedObjectsClient.closePointInTime(page2.pit_id); ``` From 1498000213ec53522ce099b044b90fa3db33b07d Mon Sep 17 00:00:00 2001 From: Justin Ibarra Date: Thu, 18 Feb 2021 05:59:46 -0900 Subject: [PATCH 40/93] [Detection Rules] Add 7.12 rules (#91082) ## Summary Pull updates to detection rules from https://github.com/elastic/detection-rules/tree/7.12 This should not merge until after #91553 is merged and backported ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) --- ...ion_email_powershell_exchange_mailbox.json | 6 +- ...ll_exch_mailbox_activesync_add_device.json | 6 +- .../collection_winrar_encryption.json | 6 +- ...d_control_certutil_network_connection.json | 5 +- ...cobalt_strike_default_teamserver_cert.json | 4 +- ...ommand_and_control_common_webservices.json | 7 +- ...nd_and_control_dns_tunneling_nslookup.json | 5 +- ...control_encrypted_channel_freesslcert.json | 5 +- ...fer_protocol_activity_to_the_internet.json | 3 +- .../command_and_control_iexplore_via_com.json | 9 +- ...hat_protocol_activity_to_the_internet.json | 3 +- ...d_control_nat_traversal_port_activity.json | 3 +- .../command_and_control_port_26_activity.json | 3 +- ...ol_port_8000_activity_to_the_internet.json | 3 +- ..._to_point_tunneling_protocol_activity.json | 3 +- ...l_proxy_port_activity_to_the_internet.json | 3 +- ...te_desktop_protocol_from_the_internet.json | 3 +- ...ol_remote_file_copy_desktopimgdownldr.json | 5 +- ...and_control_remote_file_copy_mpcmdrun.json | 5 +- ...d_control_remote_file_copy_powershell.json | 5 +- ..._and_control_remote_file_copy_scripts.json | 7 +- ...mand_and_control_smtp_to_the_internet.json | 3 +- ..._server_port_activity_to_the_internet.json | 3 +- ...ol_ssh_secure_shell_from_the_internet.json | 3 +- ...trol_ssh_secure_shell_to_the_internet.json | 3 +- ...d_control_teamviewer_remote_file_copy.json | 5 +- ...mand_and_control_telnet_port_activity.json | 3 +- ..._control_tor_activity_to_the_internet.json | 3 +- ...l_network_computing_from_the_internet.json | 3 +- ...ual_network_computing_to_the_internet.json | 3 +- ...ccess_to_browser_credentials_procargs.json | 55 + .../credential_access_cmdline_dump_tool.json | 5 +- ...ial_access_collection_sensitive_files.json | 78 ++ ...s_cookies_chromium_browsers_debugging.json | 59 + ...ess_copy_ntds_sam_volshadowcp_cmdline.json | 5 +- ...ial_access_credential_dumping_msbuild.json | 5 +- ...dential_access_credentials_keychains.json} | 15 +- ...cess_domain_backup_dpapi_private_keys.json | 5 +- ...credential_access_dump_registry_hives.json | 7 +- ...dential_access_dumping_hashes_bi_cmds.json | 49 + ...tial_access_dumping_keychain_security.json | 55 + ...ntial_access_iis_apppoolsa_pwd_appcmd.json | 5 +- ..._access_iis_connectionstrings_dumping.json | 5 +- ..._access_kerberoasting_unusual_process.json | 7 +- ...s_keychain_pwd_retrieval_security_cmd.json | 61 + ...ial_access_lsass_memdump_file_created.json | 5 +- ...l_access_mimikatz_memssp_default_logs.json | 5 +- ...ential_access_mitm_localhost_webproxy.json | 52 + ..._access_mod_wdigest_security_provider.json | 57 + ...al_access_promt_for_pwd_via_osascript.json | 13 +- ...redential_access_saved_creds_vaultcmd.json | 50 + .../credential_access_ssh_backdoor_log.json | 68 ++ .../credential_access_systemkey_dumping.json | 55 + ...den_file_attribute_with_via_attribexe.json | 5 +- ...vasion_apple_softupdates_modification.json | 58 + ...evasion_attempt_del_quarantine_attrib.json | 9 +- ...evasion_attempt_to_disable_gatekeeper.json | 49 + ...e_evasion_clearing_windows_event_logs.json | 5 +- ...vasion_clearing_windows_security_logs.json | 46 + ...efense_evasion_code_injection_conhost.json | 7 +- ...e_evasion_create_mod_root_certificate.json | 60 + .../defense_evasion_cve_2020_0601.json | 5 +- ...vasion_defender_disabled_via_registry.json | 62 + ...delete_volume_usn_journal_with_fsutil.json | 5 +- ...deleting_backup_catalogs_with_wbadmin.json | 5 +- ...e_evasion_deleting_websvr_access_logs.json | 5 +- ...deletion_of_bash_command_line_history.json | 12 +- ...ble_windows_firewall_rules_with_netsh.json | 5 +- ...vasion_dotnet_compiler_parent_process.json | 5 +- ...evasion_enable_inbound_rdp_with_netsh.json | 5 +- ...coding_or_decoding_files_via_certutil.json | 5 +- ...ense_evasion_execution_lolbas_wuauclt.json | 5 +- ...ecution_msbuild_started_by_office_app.json | 5 +- ...n_execution_msbuild_started_by_script.json | 5 +- ...ion_msbuild_started_by_system_process.json | 5 +- ...ion_execution_msbuild_started_renamed.json | 5 +- ...cution_msbuild_started_unusal_process.json | 5 +- ...execution_suspicious_explorer_winword.json | 5 +- ...ution_via_trusted_developer_utilities.json | 6 +- ..._evasion_file_creation_mult_extension.json | 53 + ...sion_hide_encoded_executable_registry.json | 5 +- ...ense_evasion_iis_httplogging_disabled.json | 5 +- .../defense_evasion_injection_msbuild.json | 5 +- ...ense_evasion_install_root_certificate.json | 58 + .../defense_evasion_installutil_beacon.json | 5 +- ...querading_as_elastic_endpoint_process.json | 5 +- ...e_evasion_masquerading_renamed_autoit.json | 5 +- ...erading_suspicious_werfault_childproc.json | 8 +- ...vasion_masquerading_trusted_directory.json | 7 +- ...defense_evasion_masquerading_werfault.json | 5 +- ...isc_lolbin_connecting_to_the_internet.json | 5 +- ...e_evasion_modification_of_boot_config.json | 5 +- ..._evasion_modify_environment_launchctl.json | 55 + ...on_msbuild_making_network_connections.json | 5 +- .../defense_evasion_mshta_beacon.json | 5 +- .../defense_evasion_msxsl_network.json | 5 +- ...etwork_connection_from_windows_binary.json | 5 +- ...vasion_port_forwarding_added_registry.json | 5 +- ...evasion_potential_processherpaderping.json | 5 +- ...cy_controls_tcc_database_modification.json | 57 + ...tion_privacy_pref_sshd_fulldiskaccess.json | 64 + ...defense_evasion_rundll32_no_arguments.json | 5 +- .../defense_evasion_safari_config_change.json | 55 + ...dboxed_office_app_suspicious_zip_file.json | 33 + ...ion_scheduledjobs_at_protocol_enabled.json | 5 +- ..._evasion_sdelete_like_filename_rename.json | 7 +- .../defense_evasion_sip_provider_mod.json | 56 + ...ackdoor_service_disabled_via_registry.json | 5 +- ...vasion_stop_process_service_threshold.json | 5 +- ...n_suspicious_managedcode_host_process.json | 5 +- ...efense_evasion_suspicious_scrobj_load.json | 7 +- ...defense_evasion_suspicious_wmi_script.json | 8 +- ...evasion_suspicious_zoom_child_process.json | 5 +- ..._critical_proc_abnormal_file_activity.json | 5 +- ...vasion_tcc_bypass_mounted_apfs_access.json | 48 + .../defense_evasion_timestomp_touch.json | 4 +- ..._evasion_unload_endpointsecurity_kext.json | 52 + ...nse_evasion_unusual_ads_file_creation.json | 53 + .../defense_evasion_unusual_dir_ads.json | 6 +- ...usual_network_connection_via_rundll32.json | 5 +- ...on_unusual_process_network_connection.json | 5 +- ...asion_unusual_system_vp_child_program.json | 5 +- .../defense_evasion_via_filter_manager.json | 6 +- ..._volume_shadow_copy_deletion_via_wmic.json | 5 +- .../discovery_adfind_command_activity.json | 5 +- .../discovery_admin_recon.json | 6 +- .../discovery_file_dir_discovery.json | 11 +- .../discovery_net_command_system_account.json | 5 +- .../prepackaged_rules/discovery_net_view.json | 6 +- .../discovery_peripheral_device.json | 6 +- ...rocess_discovery_via_tasklist_command.json | 6 +- .../discovery_query_registry_via_reg.json | 6 +- ...ote_system_discovery_commands_windows.json | 6 +- .../discovery_security_software_grep.json | 46 + .../discovery_security_software_wmic.json | 5 +- ...covery_users_domain_built_in_commands.json | 50 + .../discovery_whoami_command_activity.json | 6 +- ...arwinds_backdoor_child_cmd_powershell.json | 5 +- ...inds_backdoor_unusual_child_processes.json | 5 +- .../execution_com_object_xwizard.json | 57 + ...and_prompt_connecting_to_the_internet.json | 5 +- ...n_command_shell_started_by_powershell.json | 5 +- ...tion_command_shell_started_by_svchost.json | 5 +- ...mand_shell_started_by_unusual_process.json | 5 +- .../execution_command_shell_via_rundll32.json | 5 +- ...vasion_electron_app_childproc_node_js.json | 66 + .../execution_enumeration_via_wmiprvse.json | 46 + .../execution_from_unusual_directory.json | 5 +- .../execution_from_unusual_path_cmdline.json | 7 +- ...le_program_connecting_to_the_internet.json | 5 +- ...l_access_suspicious_browser_childproc.json | 64 + .../execution_ms_office_written_file.json | 5 +- .../execution_pdf_written_file.json | 5 +- ...on_pentest_eggshell_remote_admin_tool.json | 32 + ...ution_psexec_lateral_movement_command.json | 5 +- ...er_program_connecting_to_the_internet.json | 5 +- .../execution_revershell_via_shell_cmd.json | 51 + ...tion_scheduled_task_powershell_source.json | 7 +- ...cution_script_via_automator_workflows.json | 47 + ...xecution_shared_modules_local_sxs_dll.json | 7 +- .../execution_suspicious_cmd_wmi.json | 7 +- ...n_suspicious_image_load_wmi_ms_office.json | 8 +- ...xecution_suspicious_jar_child_process.json | 53 + .../execution_suspicious_pdf_reader.json | 5 +- ...ecution_suspicious_powershell_imgload.json | 7 +- .../execution_suspicious_psexesvc.json | 5 +- ...ecution_suspicious_short_program_name.json | 5 +- .../execution_via_compiled_html_file.json | 6 +- .../execution_via_hidden_shell_conhost.json | 5 +- .../execution_via_net_com_assemblies.json | 5 +- ...ia_xp_cmdshell_mssql_stored_procedure.json | 5 +- .../impact_hosts_file_modified.json | 5 +- ...ume_shadow_copy_deletion_via_vssadmin.json | 5 +- .../rules/prepackaged_rules/index.ts | 1084 ++++++++++------- ...ure_active_directory_high_risk_signin.json | 53 + .../initial_access_login_failures.json | 61 + .../initial_access_login_location.json | 61 + .../initial_access_login_sessions.json | 61 + .../initial_access_login_time.json | 61 + ...mote_desktop_protocol_to_the_internet.json | 3 +- ...mote_procedure_call_from_the_internet.json | 3 +- ...remote_procedure_call_to_the_internet.json | 3 +- ...al_access_script_executing_powershell.json | 5 +- ...ccess_scripts_process_started_via_wmi.json | 7 +- ...file_sharing_activity_to_the_internet.json | 3 +- ...uspicious_mac_ms_office_child_process.json | 54 + ...ss_suspicious_ms_office_child_process.json | 5 +- ...s_suspicious_ms_outlook_child_process.json | 5 +- ...l_access_unusual_dns_service_children.json | 6 +- ...ccess_unusual_dns_service_file_writes.json | 6 +- ...explorer_suspicious_child_parent_args.json | 7 +- .../lateral_movement_cmd_service.json | 7 +- ...ential_access_kerberos_bifrostconsole.json | 71 ++ .../lateral_movement_dcom_hta.json | 5 +- .../lateral_movement_dcom_mmc20.json | 5 +- ...t_dcom_shellwindow_shellbrowserwindow.json | 5 +- ...vement_direct_outbound_smb_connection.json | 5 +- ...movement_executable_tool_transfer_smb.json | 5 +- ..._movement_execution_from_tsclient_mup.json | 5 +- ...nt_execution_via_file_shares_sequence.json | 5 +- ...vement_incoming_winrm_shell_execution.json | 5 +- .../lateral_movement_incoming_wmi.json | 5 +- ...teral_movement_local_service_commands.json | 5 +- ...ment_mount_hidden_or_webdav_share_net.json | 5 +- .../lateral_movement_mounting_smb_share.json | 56 + ...l_movement_powershell_remoting_target.json | 7 +- ...lateral_movement_rdp_enabled_registry.json | 7 +- .../lateral_movement_rdp_sharprdp_target.json | 5 +- .../lateral_movement_rdp_tunnel_plink.json | 7 +- ...ovement_remote_file_copy_hidden_share.json | 5 +- .../lateral_movement_remote_services.json | 5 +- ...ral_movement_remote_ssh_login_enabled.json | 7 +- ...ateral_movement_scheduled_task_target.json | 5 +- ...ement_suspicious_rdp_client_imageload.json | 7 +- ...l_movement_via_startup_folder_rdp_smb.json | 5 +- ...teral_movement_vpn_connection_attempt.json | 50 + ...stence_account_creation_hide_at_logon.json | 55 + .../persistence_adobe_hijack_persistence.json | 5 +- .../persistence_app_compat_shim.json | 7 +- .../persistence_appcertdlls_registry.json | 6 +- .../persistence_appinitdlls_registry.json | 6 +- ..._creation_hidden_login_item_osascript.json | 75 ++ ..._access_authorization_plugin_creation.json | 57 + ...l_access_modify_auth_module_or_config.json | 71 ++ ...credential_access_modify_ssh_binaries.json | 67 + ...stence_cron_jobs_creation_and_runtime.json | 60 + ...launch_agent_deamon_logonitem_process.json | 80 ++ ...rectory_services_plugins_modification.json | 48 + ...e_docker_shortcuts_plist_modification.json | 48 + ...persistence_emond_rules_file_creation.json | 55 + ...istence_emond_rules_process_execution.json | 55 + .../persistence_enable_root_account.json | 55 + ...n_hidden_launch_agent_deamon_creation.json | 78 ++ ...evasion_hidden_local_account_creation.json | 51 + ...tence_evasion_registry_ifeo_injection.json | 6 +- ...sistence_finder_sync_plugin_pluginkit.json | 50 + ...sistence_gpo_schtask_service_creation.json | 5 +- ...ersistence_kde_autostart_modification.json | 50 + ...istence_local_scheduled_task_commands.json | 5 +- ...stence_local_scheduled_task_scripting.json | 9 +- ...stence_loginwindow_plist_modification.json | 56 + ...fication_sublime_app_plugin_or_script.json | 48 + .../persistence_ms_office_addins_file.json | 9 +- .../persistence_ms_outlook_vba_template.json | 9 +- ...ersistence_periodic_tasks_file_mdofiy.json | 57 + ...escalation_via_accessibility_features.json | 6 +- .../persistence_registry_uncommon.json | 6 +- ...persistence_run_key_and_startup_broad.json | 6 +- ...ce_runtime_run_key_startup_susp_procs.json | 6 +- .../persistence_services_registry.json | 6 +- ...ersistence_shell_profile_modification.json | 60 + ...ence_ssh_authorized_keys_modification.json | 53 + ...er_file_written_by_suspicious_process.json | 6 +- ...lder_file_written_by_unsigned_process.json | 3 +- .../persistence_startup_folder_scripts.json | 6 +- ...ence_suspicious_calendar_modification.json | 53 + ...stence_suspicious_com_hijack_registry.json | 6 +- ...s_image_load_scheduled_task_ms_office.json | 8 +- ...nce_suspicious_scheduled_task_runtime.json | 7 +- ...e_suspicious_service_created_registry.json | 6 +- ...ersistence_system_shells_via_services.json | 5 +- .../persistence_time_provider_mod.json | 56 + ..._account_added_to_privileged_group_ad.json | 57 + .../persistence_user_account_creation.json | 5 +- .../persistence_via_application_shimming.json | 6 +- ...tence_via_atom_init_file_modification.json | 32 + ...sistence_via_hidden_run_key_valuename.json | 5 +- ...sa_security_support_provider_registry.json | 6 +- ...emetrycontroller_scheduledtask_hijack.json | 5 +- ...ia_update_orchestrator_service_hijack.json | 11 +- ...nt_instrumentation_event_subscription.json | 6 +- ...calation_applescript_with_admin_privs.json | 64 + ...ilege_escalation_disable_uac_registry.json | 80 ++ ...lege_escalation_echo_nopasswd_sudoers.json | 53 + ...alation_explicit_creds_via_scripting.json} | 13 +- ...alation_exploit_adobe_acrobat_updater.json | 51 + ...lation_ld_preload_shared_object_modif.json | 56 + ..._escalation_local_user_added_to_admin.json | 55 + ...privilege_escalation_lsa_auth_package.json | 75 ++ ...e_escalation_named_pipe_impersonation.json | 6 +- ...ge_escalation_persistence_phantom_dll.json | 84 ++ ...ion_port_monitor_print_pocessor_abuse.json | 78 ++ ...ation_printspooler_registry_copyfiles.json | 7 +- ..._printspooler_service_suspicious_file.json | 7 +- ...tion_printspooler_suspicious_spl_file.json | 7 +- ...calation_rogue_windir_environment_var.json | 8 +- ...ilege_escalation_root_crontab_filemod.json | 56 + ...e_escalation_setgid_bit_set_via_chmod.json | 62 - ...tion_setuid_setgid_bit_set_via_chmod.json} | 9 +- ...ilege_escalation_sudo_buffer_overflow.json | 58 + ...privilege_escalation_sudoers_file_mod.json | 9 +- ...lege_escalation_uac_bypass_com_clipup.json | 9 +- ...ge_escalation_uac_bypass_com_ieinstal.json | 7 +- ...n_uac_bypass_com_interface_icmluautil.json | 5 +- ...alation_uac_bypass_diskcleanup_hijack.json | 5 +- ...escalation_uac_bypass_dll_sideloading.json | 5 +- ...ge_escalation_uac_bypass_event_viewer.json | 5 +- ...ege_escalation_uac_bypass_mock_windir.json | 7 +- ...scalation_uac_bypass_winfw_mmc_hijack.json | 5 +- ...tion_unusual_parentchild_relationship.json | 5 +- ...n_unusual_svchost_childproc_childless.json | 5 +- 301 files changed, 6039 insertions(+), 1003 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_access_to_browser_credentials_procargs.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cookies_chromium_browsers_debugging.json rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{credential_access_compress_credentials_keychains.json => credential_access_credentials_keychains.json} (69%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_keychain_security.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_keychain_pwd_retrieval_security_cmd.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ssh_backdoor_log.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privacy_controls_tcc_database_modification.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_users_domain_built_in_commands.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_com_object_xwizard.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_initial_access_suspicious_browser_childproc.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_via_automator_workflows.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_failures.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_location.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_sessions.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_time.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_mac_ms_office_child_process.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mounting_smb_share.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_vpn_connection_attempt.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_hidden_login_item_osascript.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_cron_jobs_creation_and_runtime.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_process_execution.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_launch_agent_deamon_creation.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_finder_sync_plugin_pluginkit.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kde_autostart_modification.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_modification_sublime_app_plugin_or_script.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_applescript_with_admin_privs.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{privilege_escalation_explicit_creds_via_apple_scripting.json => privilege_escalation_explicit_creds_via_scripting.json} (65%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setgid_bit_set_via_chmod.json rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{privilege_escalation_setuid_bit_set_via_chmod.json => privilege_escalation_setuid_setgid_bit_set_via_chmod.json} (63%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json index 5bd96c3442736b..e8b7fc59af6501 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json @@ -6,9 +6,11 @@ "false_positives": [ "Legitimate exchange system administration activity." ], + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_persistence_powershell_exch_mailbox_activesync_add_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_persistence_powershell_exch_mailbox_activesync_add_device.json index 501e30a38704c5..3080a2c8c934e1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_persistence_powershell_exch_mailbox_activesync_add_device.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_persistence_powershell_exch_mailbox_activesync_add_device.json @@ -6,9 +6,11 @@ "false_positives": [ "Legitimate exchange system administration activity." ], + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json index f7ff34fed2eeb2..9bc8912c1a3137 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies use of WinRar or 7z to create an encrypted files. Adversaries will often compress and encrypt data in preparation for exfiltration.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json index 8f81d675a43250..aff488c9a5410c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json index b3cc7bce51d725..6f3f7ca3803f35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json @@ -19,7 +19,7 @@ "https://www.elastic.co/guide/en/beats/filebeat/7.9/filebeat-module-suricata.html", "https://www.elastic.co/guide/en/beats/filebeat/7.9/filebeat-module-zeek.html" ], - "risk_score": 100, + "risk_score": 99, "rule_id": "e7075e8d-a966-458e-a183-85cd331af255", "severity": "critical", "tags": [ @@ -55,5 +55,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json index ebead5ab5e5194..b74da3bbd4d38d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Connection to Commonly Abused Web Services", - "query": "network where network.protocol == \"dns\" and\n /* Add new WebSvc domains here */\n wildcard(dns.question.name, \"*.githubusercontent.*\",\n \"*.pastebin.*\",\n \"*drive.google.*\",\n \"*docs.live.*\",\n \"*api.dropboxapi.*\",\n \"*dropboxusercontent.*\",\n \"*onedrive.*\",\n \"*4shared.*\",\n \"*.file.io\",\n \"*filebin.net\",\n \"*slack-files.com\",\n \"*ghostbin.*\",\n \"*ngrok.*\",\n \"*portmap.*\",\n \"*serveo.net\",\n \"*localtunnel.me\",\n \"*pagekite.me\",\n \"*localxpose.io\",\n \"*notabug.org\"\n ) and\n /* Insert noisy false positives here */\n not process.name in (\"MicrosoftEdgeCP.exe\",\n \"MicrosoftEdge.exe\",\n \"iexplore.exe\",\n \"chrome.exe\",\n \"msedge.exe\",\n \"opera.exe\",\n \"firefox.exe\",\n \"Dropbox.exe\",\n \"slack.exe\",\n \"svchost.exe\",\n \"thunderbird.exe\",\n \"outlook.exe\",\n \"OneDrive.exe\")\n", + "query": "network where network.protocol == \"dns\" and\n /* Add new WebSvc domains here */\n dns.question.name :\n (\n \"*.githubusercontent.*\",\n \"*.pastebin.*\",\n \"*drive.google.*\",\n \"*docs.live.*\",\n \"*api.dropboxapi.*\",\n \"*dropboxusercontent.*\",\n \"*onedrive.*\",\n \"*4shared.*\",\n \"*.file.io\",\n \"*filebin.net\",\n \"*slack-files.com\",\n \"*ghostbin.*\",\n \"*ngrok.*\",\n \"*portmap.*\",\n \"*serveo.net\",\n \"*localtunnel.me\",\n \"*pagekite.me\",\n \"*localxpose.io\",\n \"*notabug.org\"\n ) and\n /* Insert noisy false positives here */\n not process.name :\n (\n \"MicrosoftEdgeCP.exe\",\n \"MicrosoftEdge.exe\",\n \"iexplore.exe\",\n \"chrome.exe\",\n \"msedge.exe\",\n \"opera.exe\",\n \"firefox.exe\",\n \"Dropbox.exe\",\n \"slack.exe\",\n \"svchost.exe\",\n \"thunderbird.exe\",\n \"outlook.exe\",\n \"OneDrive.exe\"\n )\n", "risk_score": 21, "rule_id": "66883649-f908-4a5b-a1e0-54090a1d3a32", "severity": "low", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json index 8e9822cc610a7b..d4321263059d7f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -47,5 +48,5 @@ "value": 15 }, "type": "threshold", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json index 40f5e928b9c5fe..809242615d9941 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json index 4aabfe552f0fd4..a7c657eae5aa86 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "FTP servers should be excluded from this rule as this is expected behavior. Some business workflows may use FTP for data exchange. These workflows often have expected characteristics such as users, sources, and destinations. FTP activity involving an unusual source or destination may be more suspicious. FTP activity involving a production server that has no known associated FTP workflow or business requirement is often suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json index b0718fc2418be8..de1b3da964a8c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Potential Command and Control via Internet Explorer", - "query": "sequence by host.id, process.entity_id with maxspan = 1s\n [process where event.type:\"start\" and process.parent.name:\"iexplore.exe\" and process.parent.args:\"-Embedding\"]\n /* IE started via COM in normal conditions makes few connections, mainly to Microsoft and OCSP related domains, add FPs here */\n [network where network.protocol : \"dns\" and process.name:\"iexplore.exe\" and\n not wildcard(dns.question.name, \"*.microsoft.com\", \n \"*.digicert.com\", \n \"*.msocsp.com\", \n \"*.windowsupdate.com\", \n \"*.bing.com\",\n \"*.identrust.com\")\n ]\n", - "risk_score": 43, + "query": "sequence by host.id, process.entity_id with maxspan = 1s\n [process where event.type == \"start\" and process.parent.name : \"iexplore.exe\" and process.parent.args : \"-Embedding\"]\n /* IE started via COM in normal conditions makes few connections, mainly to Microsoft and OCSP related domains, add FPs here */\n [network where network.protocol == \"dns\" and process.name : \"iexplore.exe\" and\n not dns.question.name :\n (\n \"*.microsoft.com\",\n \"*.digicert.com\",\n \"*.msocsp.com\",\n \"*.windowsupdate.com\",\n \"*.bing.com\",\n \"*.identrust.com\"\n )\n ]\n", + "risk_score": 47, "rule_id": "acd611f3-2b93-47b3-a0a3-7723bcc46f6d", "severity": "medium", "tags": [ @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json index 2ad30a3d376a12..80a02e38877200 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "IRC activity may be normal behavior for developers and engineers but is unusual for non-engineering end users. IRC activity involving an unusual source or destination may be more suspicious. IRC activity involving a production server is often suspicious. Because these ports are in the ephemeral range, this rule may false under certain conditions, such as when a NAT-ed web server replies to a client which has used a port in the range by coincidence. In this case, these servers can be excluded. Some legacy applications may use these ports, but this is very uncommon and usually only appears in local traffic using private IPs, which does not match this rule's conditions." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json index 7553dfefca68fb..777829a0076970 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Some networks may utilize these protocols but usage that is unfamiliar to local network administrators can be unexpected and suspicious. Because this port is in the ephemeral range, this rule may false under certain conditions, such as when an application server with a public IP address replies to a client which has used a UDP port in the range by coincidence. This is uncommon but such servers can be excluded." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json index 6dae38320e19a9..2f33f7d3f43a8b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Servers that process email traffic may cause false positives and should be excluded from this rule as this is expected behavior." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -57,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_8000_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_8000_activity_to_the_internet.json index fcdd23f84c8896..b90fb4a0d389c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_8000_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_8000_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Because this port is in the ephemeral range, this rule may false under certain conditions, such as when a NATed web server replies to a client which has used a port in the range by coincidence. In this case, such servers can be excluded. Some applications may use this port but this is very uncommon and usually appears in local traffic using private IPs, which this rule does not match. Some cloud environments, particularly development environments, may use this port when VPNs or direct connects are not in use and cloud instances are accessed across the Internet." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_pptp_point_to_point_tunneling_protocol_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_pptp_point_to_point_tunneling_protocol_activity.json index 572be2fad80fe7..15d042b7fe00c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_pptp_point_to_point_tunneling_protocol_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_pptp_point_to_point_tunneling_protocol_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "Some networks may utilize PPTP protocols but this is uncommon as more modern VPN technologies are available. Usage that is unfamiliar to local network administrators can be unexpected and suspicious. Torrenting applications may use this port. Because this port is in the ephemeral range, this rule may false under certain conditions, such as when an application server replies to a client that used this port by coincidence. This is uncommon but such servers can be excluded." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_proxy_port_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_proxy_port_activity_to_the_internet.json index 51fc3c17f7e5e9..533edfb6f74412 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_proxy_port_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_proxy_port_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Some proxied applications may use these ports but this usually occurs in local traffic using private IPs which this rule does not match. Proxies are widely used as a security technology but in enterprise environments this is usually local traffic which this rule does not match. If desired, internet proxy services using these ports can be added to allowlists. Some screen recording applications may use these ports. Proxy port activity involving an unusual source or destination may be more suspicious. Some cloud environments may use this port when VPNs or direct connects are not in use and cloud instances are accessed across the Internet. Because these ports are in the ephemeral range, this rule may false under certain conditions such as when a NATed web server replies to a client which has used a port in the range by coincidence. In this case, such servers can be excluded if desired." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json index 9443a7d264562f..f8918c0420963e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Some network security policies allow RDP directly from the Internet but usage that is unfamiliar to server or network owners can be unexpected and suspicious. RDP services may be exposed directly to the Internet in some networks such as cloud environments. In such cases, only RDP gateways, bastions or jump servers may be expected expose RDP directly to the Internet and can be exempted from this rule. RDP may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -68,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json index 1e6dc210127c06..e4e1c4dee0b16f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json index 23842591116161..dd3003e346ab4e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -46,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json index 5e0a6f8e3e25e1..c5f5b8cca1eede 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -62,5 +63,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json index 37f1364c5f61fe..a1cbec37c591ba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Remote File Download via Script Interpreter", "query": "sequence by host.id, process.entity_id\n [network where process.name : (\"wscript.exe\", \"cscript.exe\") and network.protocol != \"dns\" and\n network.direction == \"outgoing\" and network.type == \"ipv4\" and destination.ip != \"127.0.0.1\"\n ]\n [file where event.type == \"creation\" and file.extension : (\"exe\", \"dll\")]\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "1d276579-3380-4095-ad38-e596a01bc64f", "severity": "medium", "tags": [ @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_smtp_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_smtp_to_the_internet.json index 95b07dab428271..5d6efd0802351b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_smtp_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_smtp_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "NATed servers that process email traffic may false and should be excluded from this rule as this is expected behavior for them. Consumer and personal devices may send email traffic to remote Internet destinations. In this case, such devices or networks can be excluded from this rule if this is expected behavior." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sql_server_port_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sql_server_port_activity_to_the_internet.json index 4bd7559e3878f6..e7a5e4c386fce0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sql_server_port_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sql_server_port_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Because these ports are in the ephemeral range, this rule may false under certain conditions such as when a NATed web server replies to a client which has used a port in the range by coincidence. In this case, such servers can be excluded if desired. Some cloud environments may use this port when VPNs or direct connects are not in use and database instances are accessed directly across the Internet." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_from_the_internet.json index f081fba9c1babe..ff97c392ccbe64 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_from_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Some network security policies allow SSH directly from the Internet but usage that is unfamiliar to server or network owners can be unexpected and suspicious. SSH services may be exposed directly to the Internet in some networks such as cloud environments. In such cases, only SSH gateways, bastions or jump servers may be expected expose SSH directly to the Internet and can be exempted from this rule. SSH may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -68,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_to_the_internet.json index b92bf9065693aa..fdf4dfe0e8a7e0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ssh_secure_shell_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "SSH connections may be made directly to Internet destinations in order to access Linux cloud server instances but such connections are usually made only by engineers. In such cases, only SSH gateways, bastions or jump servers may be expected Internet destinations and can be exempted from this rule. SSH may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -38,5 +39,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json index e83fbcc58f4cda..0bf7ac92fdaf69 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json index 747e3b87d4e49c..5c570a2471eaea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json @@ -6,6 +6,7 @@ "false_positives": [ "IoT (Internet of Things) devices and networks may use telnet and can be excluded if desired. Some business work-flows may use Telnet for administration of older devices. These often have a predictable behavior. Telnet activity involving an unusual source or destination may be more suspicious. Telnet activity involving a production server that has no known associated Telnet work-flow or business requirement is often suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -68,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_tor_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_tor_activity_to_the_internet.json index 38666db032c579..7d302ba4062a51 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_tor_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_tor_activity_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "Tor client activity is uncommon in managed enterprise networks but may be common in unmanaged or public networks where few security policies apply. Because these ports are in the ephemeral range, this rule may false under certain conditions such as when a NATed web server replies to a client which has used one of these ports by coincidence. In this case, such servers can be excluded if desired." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json index 53572c125f4e77..81789b415378cc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "VNC connections may be received directly to Linux cloud server instances but such connections are usually made only by engineers. VNC is less common than SSH or RDP but may be required by some work-flows such as remote access and support for specialized software products or servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -59,5 +60,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json index 06b55dc499d052..9ceae3f436a7f6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "VNC connections may be made directly to Linux cloud server instances but such connections are usually made only by engineers. VNC is less common than SSH or RDP but may be required by some work flows such as remote access and support for specialized software products or servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_access_to_browser_credentials_procargs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_access_to_browser_credentials_procargs.json new file mode 100644 index 00000000000000..4473933bf8521b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_access_to_browser_credentials_procargs.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a process with arguments pointing to known browser files that store passwords and cookies. Adversaries may acquire credentials from web browsers by reading files specific to the target browser.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Access of Stored Browser Credentials", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.args :\n (\n \"/Users/*/Library/Application Support/Google/Chrome/Default/Login Data\", \n \"/Users/*/Library/Application Support/Google/Chrome/Default/Cookies\", \n \"/Users/*/Library/Cookies*\", \n \"/Users/*/Library/Application Support/Firefox/Profiles/*.default/cookies.sqlite\", \n \"/Users/*/Library/Application Support/Firefox/Profiles/*.default/key*.db\", \n \"/Users/*/Library/Application Support/Firefox/Profiles/*.default/logins.json\", \n \"Login Data\",\n \"Cookies.binarycookies\", \n \"key4.db\", \n \"key3.db\", \n \"logins.json\", \n \"cookies.sqlite\"\n )\n", + "references": [ + "https://securelist.com/calisto-trojan-for-macos/86543/" + ], + "risk_score": 73, + "rule_id": "20457e4f-d1de-4b92-ae69-142e27a4342a", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1555", + "name": "Credentials from Password Stores", + "reference": "https://attack.mitre.org/techniques/T1555/", + "subtechnique": [ + { + "id": "T1555.003", + "name": "Credentials from Web Browsers", + "reference": "https://attack.mitre.org/techniques/T1555/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json index dbb9c1af14d846..a411ccecc1fc9b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json new file mode 100644 index 00000000000000..d7dbb660b7d612 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json @@ -0,0 +1,78 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the use of a compression utility to collect known files containing sensitive information, such as credentials and system configurations.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Sensitive Files Compression", + "query": "event.category:process and event.type:start and process.name:(zip or tar or gzip or hdiutil or 7z) and process.args: ( /root/.ssh/id_rsa or /root/.ssh/id_rsa.pub or /root/.ssh/id_ed25519 or /root/.ssh/id_ed25519.pub or /root/.ssh/authorized_keys or /root/.ssh/authorized_keys2 or /root/.ssh/known_hosts or /root/.bash_history or /etc/hosts or /home/*/.ssh/id_rsa or /home/*/.ssh/id_rsa.pub or /home/*/.ssh/id_ed25519 or /home/*/.ssh/id_ed25519.pub or /home/*/.ssh/authorized_keys or /home/*/.ssh/authorized_keys2 or /home/*/.ssh/known_hosts or /home/*/.bash_history or /root/.aws/credentials or /root/.aws/config or /home/*/.aws/credentials or /home/*/.aws/config or /root/.docker/config.json or /home/*/.docker/config.json or /etc/group or /etc/passwd or /etc/shadow or /etc/gshadow )", + "references": [ + "https://www.trendmicro.com/en_ca/research/20/l/teamtnt-now-deploying-ddos-capable-irc-bot-tntbotinger.html" + ], + "risk_score": 47, + "rule_id": "6b84d470-9036-4cc0-a27c-6d90bbfe81ab", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Collection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1552", + "name": "Unsecured Credentials", + "reference": "https://attack.mitre.org/techniques/T1552/", + "subtechnique": [ + { + "id": "T1552.001", + "name": "Credentials In Files", + "reference": "https://attack.mitre.org/techniques/T1552/001/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0009", + "name": "Collection", + "reference": "https://attack.mitre.org/tactics/TA0009/" + }, + "technique": [ + { + "id": "T1560", + "name": "Archive Collected Data", + "reference": "https://attack.mitre.org/techniques/T1560/", + "subtechnique": [ + { + "id": "T1560.001", + "name": "Archive via Utility", + "reference": "https://attack.mitre.org/techniques/T1560/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cookies_chromium_browsers_debugging.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cookies_chromium_browsers_debugging.json new file mode 100644 index 00000000000000..eb798dcc2cb285 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cookies_chromium_browsers_debugging.json @@ -0,0 +1,59 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a Chromium based browser with the debugging process argument, which may indicate an attempt to steal authentication cookies. An adversary may steal web application or service session cookies and use them to gain access web applications or Internet services as an authenticated user without needing credentials.", + "false_positives": [ + "Developers performing browsers plugin or extension debugging." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "max_signals": 33, + "name": "Potential Cookies Theft via Browser Debugging", + "query": "process where event.type in (\"start\", \"process_started\", \"info\") and\n process.name in (\n \"Microsoft Edge\",\n \"chrome.exe\",\n \"Google Chrome\",\n \"google-chrome-stable\",\n \"google-chrome-beta\",\n \"google-chrome\",\n \"msedge.exe\") and\n process.args : (\"--remote-debugging-port=*\", \n \"--remote-debugging-targets=*\", \n \"--remote-debugging-pipe=*\") and\n process.args : \"--user-data-dir=*\" and not process.args:\"--remote-debugging-port=0\"\n", + "references": [ + "https://github.com/defaultnamehere/cookie_crimes", + "https://embracethered.com/blog/posts/2020/cookie-crimes-on-mirosoft-edge/", + "https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/post/multi/gather/chrome_cookies.md", + "https://posts.specterops.io/hands-in-the-cookie-jar-dumping-cookies-with-chromiums-remote-debugger-port-34c4f468844e" + ], + "risk_score": 47, + "rule_id": "027ff9ea-85e7-42e3-99d2-bbb7069e02eb", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Windows", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1539", + "name": "Steal Web Session Cookie", + "reference": "https://attack.mitre.org/techniques/T1539/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json index 1750bd180b6af3..8df6a9c7199571 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json index b3f83e24655eb3..289b959ece15ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_compress_credentials_keychains.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credentials_keychains.json similarity index 69% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_compress_credentials_keychains.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credentials_keychains.json index 51e008c848b495..a70ff26c3c0c70 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_compress_credentials_keychains.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credentials_keychains.json @@ -2,18 +2,19 @@ "author": [ "Elastic" ], - "description": "Adversaries may collect the keychain storage data from a system to acquire credentials. Keychains are the built-in way for macOS to keep track of users' passwords and credentials for many services and features such as WiFi passwords, websites, secure notes, certificates, and Kerberos.", + "description": "Adversaries may collect the keychain storage data from a system to acquire credentials. Keychains are the built-in way for macOS to keep track of users' passwords and credentials for many services and features such as WiFi passwords, websites, secure notes and certificates.", "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" ], - "language": "kuery", + "language": "eql", "license": "Elastic License", - "name": "Compression of Keychain Credentials Directories", - "query": "event.category:process and event.type:(start or process_started) and process.name:(zip or tar or gzip or 7za or hdiutil) and process.args:(\"/Library/Keychains/\" or \"/Network/Library/Keychains/\" or \"~/Library/Keychains/\")", + "name": "Access to Keychain Credentials Directories", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.args :\n (\n \"/Users/*/Library/Keychains/*\",\n \"/Library/Keychains/*\",\n \"/Network/Library/Keychains/*\",\n \"System.keychain\",\n \"login.keychain-db\",\n \"login.keychain\"\n )\n", "references": [ - "https://objective-see.com/blog/blog_0x25.html" + "https://objective-see.com/blog/blog_0x25.html", + "https://securelist.com/calisto-trojan-for-macos/86543/" ], "risk_score": 73, "rule_id": "96e90768-c3b7-4df6-b5d9-6237f8bc36a8", @@ -50,6 +51,6 @@ } ], "timestamp_override": "event.ingested", - "type": "query", - "version": 3 + "type": "eql", + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json index f3c6b1c785fe57..6188dbe9fc0c02 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json index 474f4f0f7c617c..5cb84bb2a2ef22 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Credential Acquisition via Registry Hive Dumping", - "query": "process where event.type in (\"start\", \"process_started\") and\n process.pe.original_file_name == \"reg.exe\" and\n process.args : (\"save\", \"export\") and\n process.args : (\"hklm\\\\sam\", \"hklm\\\\security\") and\n not process.parent.executable : \"C:\\\\Program Files*\\\\Rapid7\\\\Insight Agent\\\\components\\\\insight_agent\\\\*\\\\ir_agent.exe\"\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.pe.original_file_name == \"reg.exe\" and\n process.args : (\"save\", \"export\") and\n process.args : (\"hklm\\\\sam\", \"hklm\\\\security\")\n", "references": [ "https://medium.com/threatpunter/detecting-attempts-to-steal-passwords-from-the-registry-7512674487f8" ], @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json new file mode 100644 index 00000000000000..f27e8c4272c09a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json @@ -0,0 +1,49 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of macOS built-in commands used to dump user account hashes. Adversaries may attempt to dump credentials to obtain account login information in the form of a hash. These hashes can be cracked or leveraged for lateral movement.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Dumping Account Hashes via Built-In Commands", + "query": "event.category:process and event.type:start and process.name:(defaults or mkpassdb) and process.args:(ShadowHashData or \"-dump\")", + "references": [ + "https://apple.stackexchange.com/questions/186893/os-x-10-9-where-are-password-hashes-stored", + "https://www.unix.com/man-page/osx/8/mkpassdb/" + ], + "risk_score": 73, + "rule_id": "02ea4563-ec10-4974-b7de-12e65aa4f9b3", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1003", + "name": "OS Credential Dumping", + "reference": "https://attack.mitre.org/techniques/T1003/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_keychain_security.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_keychain_security.json new file mode 100644 index 00000000000000..f8524c589ea0f4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_keychain_security.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may dump the content of the keychain storage data from a system to acquire credentials. Keychains are the built-in way for macOS to keep track of users' passwords and credentials for many services and features, including Wi-Fi and website passwords, secure notes, certificates, and Kerberos.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Dumping of Keychain Content via Security Command", + "query": "process where event.type in (\"start\", \"process_started\") and process.args : \"dump-keychain\" and process.args : \"-d\"\n", + "references": [ + "https://ss64.com/osx/security.html" + ], + "risk_score": 73, + "rule_id": "565d6ca5-75ba-4c82-9b13-add25353471c", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1555", + "name": "Credentials from Password Stores", + "reference": "https://attack.mitre.org/techniques/T1555/", + "subtechnique": [ + { + "id": "T1555.001", + "name": "Keychain", + "reference": "https://attack.mitre.org/techniques/T1555/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json index 428272d6447cbd..830721ef464542 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json index f810ba740738db..2dab1711f9009c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -46,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json index d6a6aa14cba260..d4251313f75dea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json @@ -9,13 +9,14 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Kerberos Traffic from Unusual Process", "query": "network where event.type == \"start\" and network.direction == \"outgoing\" and\n destination.port == 88 and source.port >= 49152 and\n process.executable != \"C:\\\\Windows\\\\System32\\\\lsass.exe\" and destination.address !=\"127.0.0.1\" and destination.address !=\"::1\" and\n /* insert False Positives here */\n not process.name in (\"swi_fc.exe\", \"fsIPcam.exe\", \"IPCamera.exe\", \"MicrosoftEdgeCP.exe\", \"MicrosoftEdge.exe\", \"iexplore.exe\", \"chrome.exe\", \"msedge.exe\", \"opera.exe\", \"firefox.exe\")\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "897dc6b5-b39f-432a-8d75-d3730d50c782", "severity": "medium", "tags": [ @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_keychain_pwd_retrieval_security_cmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_keychain_pwd_retrieval_security_cmd.json new file mode 100644 index 00000000000000..74cfa19caf1ea3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_keychain_pwd_retrieval_security_cmd.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may collect keychain storage data from a system to in order to acquire credentials. Keychains are the built-in way for macOS to keep track of users' passwords and credentials for many services and features, including Wi-Fi and website passwords, secure notes, certificates, and Kerberos.", + "false_positives": [ + "Trusted parent processes accessing their respective application passwords." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Keychain Password Retrieval via Command Line", + "query": "event.category:process and event.type:(start or process_started) and process.name:security and process.args:(\"find-generic-password\" or \"find-internet-password\")", + "references": [ + "https://www.netmeister.org/blog/keychain-passwords.html", + "https://github.com/priyankchheda/chrome_password_grabber/blob/master/chrome.py", + "https://ss64.com/osx/security.html", + "https://www.intezer.com/blog/research/operation-electrorat-attacker-creates-fake-companies-to-drain-your-crypto-wallets/" + ], + "risk_score": 73, + "rule_id": "9092cd6c-650f-4fa3-8a8a-28256c7489c9", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1555", + "name": "Credentials from Password Stores", + "reference": "https://attack.mitre.org/techniques/T1555/", + "subtechnique": [ + { + "id": "T1555.001", + "name": "Keychain", + "reference": "https://attack.mitre.org/techniques/T1555/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json index ef1d14add5b218..c1e0f9f7e7a638 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json index 3a037985b61485..8f1090f99414e7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json new file mode 100644 index 00000000000000..311723651cb3e1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json @@ -0,0 +1,52 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the use of the built-in networksetup command to configure webproxy settings. This may indicate an attempt to hijack web browser traffic for credential access via traffic sniffing or redirection.", + "false_positives": [ + "Legitimate WebProxy Settings Modification" + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "WebProxy Settings Modification", + "query": "event.category:process and event.type:start and process.name:networksetup and process.args:(\"-setwebproxy\" or \"-setsecurewebproxy\" or \"-setautoproxyurl\")", + "references": [ + "https://unit42.paloaltonetworks.com/mac-malware-steals-cryptocurrency-exchanges-cookies/", + "https://objectivebythesea.com/v2/talks/OBTS_v2_Zohar.pdf" + ], + "risk_score": 47, + "rule_id": "10a500bb-a28f-418e-ba29-ca4c8d1a9f2f", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1539", + "name": "Steal Web Session Cookie", + "reference": "https://attack.mitre.org/techniques/T1539/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json new file mode 100644 index 00000000000000..84f1315472d7a1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to modify the WDigest security provider in the registry to force the user's password to be stored in clear text in memory. This behavior can be indicative of an adversary attempting to weaken the security configuration of an endpoint. Once the UseLogonCredential value is modified, the adversary may attempt to dump clear text passwords from memory.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Modification of WDigest Security Provider", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path:\"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\SecurityProviders\\\\WDigest\\\\UseLogonCredential\" and\n registry.data.strings:\"1\"\n", + "references": [ + "https://www.csoonline.com/article/3438824/how-to-detect-and-halt-credential-theft-via-windows-wdigest.html", + "https://www.praetorian.com/blog/mitigating-mimikatz-wdigest-cleartext-credential-theft?edition=2019" + ], + "risk_score": 73, + "rule_id": "d703a5af-d5b0-43bd-8ddb-7a5d500b7da5", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1003", + "name": "OS Credential Dumping", + "reference": "https://attack.mitre.org/techniques/T1003/", + "subtechnique": [ + { + "id": "T1003.001", + "name": "LSASS Memory", + "reference": "https://attack.mitre.org/techniques/T1003/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json index f8bb27bf1d822c..e369c6e87d3e02 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License", "name": "Prompt for Credentials with OSASCRIPT", - "query": "process where event.type in (\"start\", \"process_started\") and process.name:\"osascript\" and process.args:\"-e\" and process.args:\"password\"\n", + "query": "process where event.type in (\"start\", \"process_started\") and process.name : \"osascript\" and\n process.command_line : \"osascript*display dialog*password*\"\n", "references": [ "https://github.com/EmpireProject/EmPyre/blob/master/lib/modules/collection/osx/prompt.py", "https://ss64.com/osx/osascript.html" @@ -38,12 +38,19 @@ { "id": "T1056", "name": "Input Capture", - "reference": "https://attack.mitre.org/techniques/T1056/" + "reference": "https://attack.mitre.org/techniques/T1056/", + "subtechnique": [ + { + "id": "T1056.002", + "name": "GUI Input Capture", + "reference": "https://attack.mitre.org/techniques/T1056/002/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json new file mode 100644 index 00000000000000..f9efa7f47b9961 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Windows Credential Manager allows you to create, view, or delete saved credentials for signing into websites, connected applications, and networks. An adversary may abuse this to list or dump credentials stored in the Credential Manager for saved usernames and passwords. This may also be performed in preparation of lateral movement.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Searching for Saved Credentials via VaultCmd", + "query": "process where event.type in (\"start\", \"process_started\") and\n (process.pe.original_file_name:\"vaultcmd.exe\" or process.name:\"vaultcmd.exe\") and\n process.args:\"/list*\"\n", + "references": [ + "https://medium.com/threatpunter/detecting-adversary-tradecraft-with-image-load-event-logging-and-eql-8de93338c16", + "https://rastamouse.me/blog/rdp-jump-boxes/" + ], + "risk_score": 47, + "rule_id": "be8afaed-4bcd-4e0a-b5f9-5562003dde81", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1003", + "name": "OS Credential Dumping", + "reference": "https://attack.mitre.org/techniques/T1003/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ssh_backdoor_log.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ssh_backdoor_log.json new file mode 100644 index 00000000000000..873763ce3048c6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ssh_backdoor_log.json @@ -0,0 +1,68 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies a Secure Shell (SSH) client or server process creating or writing to a known SSH backdoor log file. Adversaries may modify SSH related binaries for persistence or credential access via patching sensitive functions to enable unauthorized access or to log SSH credentials for exfiltration.", + "false_positives": [ + "Updates to approved and trusted SSH executables can trigger this rule." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential OpenSSH Backdoor Logging Activity", + "query": "file where event.type == \"change\" and process.executable : (\"/usr/sbin/sshd\", \"/usr/bin/ssh\") and\n (\n file.name : (\".*\", \"~*\") or\n file.extension : (\"in\", \"out\", \"ini\", \"h\", \"gz\", \"so\", \"sock\", \"sync\", \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\") or\n file.path : \n (\n \"/private/etc/*--\", \n \"/usr/share/*\", \n \"/usr/include/*\", \n \"/usr/local/include/*\", \n \"/private/tmp/*\", \n \"/private/var/tmp/*\",\n \"/usr/tmp/*\", \n \"/usr/share/man/*\", \n \"/usr/local/share/*\", \n \"/usr/lib/*.so.*\", \n \"/private/etc/ssh/.sshd_auth\",\n \"/usr/bin/ssd\", \n \"/private/var/opt/power\", \n \"/private/etc/ssh/ssh_known_hosts\", \n \"/private/var/html/lol\", \n \"/private/var/log/utmp\", \n \"/private/var/lib\",\n \"/var/run/sshd/sshd.pid\",\n \"/var/run/nscd/ns.pid\",\n \"/var/run/udev/ud.pid\",\n \"/var/run/udevd.pid\"\n )\n )\n", + "references": [ + "https://github.com/eset/malware-ioc/tree/master/sshdoor", + "https://www.welivesecurity.com/wp-content/uploads/2021/01/ESET_Kobalos.pdf" + ], + "risk_score": 73, + "rule_id": "f28e2be4-6eca-4349-bdd9-381573730c22", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Persistence", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1556", + "name": "Modify Authentication Process", + "reference": "https://attack.mitre.org/techniques/T1556/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1554", + "name": "Compromise Client Software Binary", + "reference": "https://attack.mitre.org/techniques/T1554/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json new file mode 100644 index 00000000000000..e6ca43ead126ce --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Keychains are the built-in way for macOS to keep track of users' passwords and credentials for many services and features, including Wi-Fi and website passwords, secure notes, certificates, and Kerberos. Adversaries may collect the keychain storage data from a system to acquire credentials.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "SystemKey Access via Command Line", + "query": "event.category:process and event.type:(start or process_started) and process.args:\"/private/var/db/SystemKey\"", + "references": [ + "https://github.com/AlessandroZ/LaZagne/blob/master/Mac/lazagne/softwares/system/chainbreaker.py" + ], + "risk_score": 73, + "rule_id": "d75991f2-b989-419d-b797-ac1e54ec2d61", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1555", + "name": "Credentials from Password Stores", + "reference": "https://attack.mitre.org/techniques/T1555/", + "subtechnique": [ + { + "id": "T1555.001", + "name": "Keychain", + "reference": "https://attack.mitre.org/techniques/T1555/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json index 878f2532cf1c51..ea97da66d52cf0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -57,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json new file mode 100644 index 00000000000000..5c577a494b1794 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json @@ -0,0 +1,58 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies changes to the SoftwareUpdate preferences using the built-in defaults command. Adversaries may abuse this in an attempt to disable security updates.", + "false_positives": [ + "Authorized SoftwareUpdate Settings Changes" + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "SoftwareUpdate Preferences Modification", + "query": "event.category:process and event.type:(start or process_started) and process.name:defaults and process.args:(write and \"-bool\" and (com.apple.SoftwareUpdate or /Library/Preferences/com.apple.SoftwareUpdate.plist) and not (TRUE or true))", + "references": [ + "https://blog.checkpoint.com/2017/07/13/osxdok-refuses-go-away-money/" + ], + "risk_score": 47, + "rule_id": "f683dcdf-a018-4801-b066-193d4ae6c8e5", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json index bfa8c19d8ea779..4ecb25a2d6de1c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json @@ -11,11 +11,12 @@ "language": "eql", "license": "Elastic License", "name": "Attempt to Remove File Quarantine Attribute", - "query": "process where event.type in (\"start\", \"process_started\") and\n process.name == \"xattr\" and process.args == \"com.apple.quarantine\" and process.args == \"-d\"\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.args : \"xattr\" and\n (\n (process.args : \"com.apple.quarantine\" and process.args : (\"-d\", \"-w\")) or\n (process.args : \"-c\" and process.command_line :\n (\n \"/bin/bash -c xattr -c *\",\n \"/bin/zsh -c xattr -c *\",\n \"/bin/sh -c xattr -c *\"\n )\n )\n )\n", "references": [ - "https://www.trendmicro.com/en_us/research/20/k/new-macos-backdoor-connected-to-oceanlotus-surfaces.html" + "https://www.trendmicro.com/en_us/research/20/k/new-macos-backdoor-connected-to-oceanlotus-surfaces.html", + "https://ss64.com/osx/xattr.html" ], - "risk_score": 43, + "risk_score": 47, "rule_id": "f0b48bbc-549e-4bcf-8ee0-a7a72586c6a7", "severity": "medium", "tags": [ @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json new file mode 100644 index 00000000000000..e351a48e2b1eb6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json @@ -0,0 +1,49 @@ +{ + "author": [ + "Elastic" + ], + "description": "Detects attempts to disable Gatekeeper on macOS. Gatekeeper is a security feature that's designed to ensure that only trusted software is run. Adversaries may attempt to disable Gatekeeper before executing malicious code.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Attempt to Disable Gatekeeper", + "query": "event.category:process and event.type:(start or process_started) and process.args:(spctl and \"--master-disable\")", + "references": [ + "https://support.apple.com/en-us/HT202491", + "https://www.carbonblack.com/blog/tau-threat-intelligence-notification-new-macos-malware-variant-of-shlayer-osx-discovered/" + ], + "risk_score": 47, + "rule_id": "4da13d6e-904f-4636-81d8-6ab14b4e6ae9", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1553", + "name": "Subvert Trust Controls", + "reference": "https://attack.mitre.org/techniques/T1553/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json index d99f2f90e130a1..cc5b948fbf20f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json new file mode 100644 index 00000000000000..0a19ea075926bb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json @@ -0,0 +1,46 @@ +{ + "author": [ + "Elastic", + "Anabella Cristaldi" + ], + "description": "Identifies attempts to clear Windows event log stores. This is often done by attackers in an attempt to evade detection or destroy forensic evidence on a system.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Windows Event Logs Cleared", + "query": "event.action:(\"audit-log-cleared\" or \"Log clear\")", + "risk_score": 21, + "rule_id": "45ac4800-840f-414c-b221-53dd36a5aaf7", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1070", + "name": "Indicator Removal on Host", + "reference": "https://attack.mitre.org/techniques/T1070/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_code_injection_conhost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_code_injection_conhost.json index 00c7a5fce40528..c5a9c7e315920b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_code_injection_conhost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_code_injection_conhost.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", "name": "Suspicious Process from Conhost", - "query": "event.category:process and event.type:(start or process_started) and process.parent.name:conhost.exe", + "query": "event.category:process and event.type:(start or process_started) and process.parent.name:conhost.exe and not process.executable:(\"C:\\Windows\\splwow64.exe\" or \"C:\\Windows\\System32\\WerFault.exe\" or \"C:\\\\Windows\\System32\\conhost.exe\")", "references": [ "https://modexp.wordpress.com/2018/09/12/process-injection-user-data/", "https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES/blob/master/Defense%20Evasion/evasion_codeinj_odzhan_conhost_sysmon_10_1.evtx" @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json new file mode 100644 index 00000000000000..9ec4e7a9a0ab8a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json @@ -0,0 +1,60 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of a local trusted root certificate in Windows. The install of a malicious root certificate would allow an attacker the ability to masquerade malicious files as valid signed components from any entity (e.g. Microsoft). It could also allow an attacker to decrypt SSL traffic.", + "false_positives": [ + "Certain applications may install root certificates for the purpose of inspecting SSL traffic." + ], + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Creation or Modification of Root Certificate", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path :\n (\n \"HKLM\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\"\n )\n", + "references": [ + "https://posts.specterops.io/code-signing-certificate-cloning-attacks-and-defenses-6f98657fc6ec", + "https://www.ired.team/offensive-security/persistence/t1130-install-root-certificate" + ], + "risk_score": 21, + "rule_id": "203ab79b-239b-4aa5-8e54-fc50623ee8e4", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1553", + "name": "Subvert Trust Controls", + "reference": "https://attack.mitre.org/techniques/T1553/", + "subtechnique": [ + { + "id": "T1553.004", + "name": "Install Root Certificate", + "reference": "https://attack.mitre.org/techniques/T1553/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json index f97901404fdc76..ddd3e20ac1c7cd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json @@ -4,7 +4,8 @@ ], "description": "A spoofing vulnerability exists in the way Windows CryptoAPI (Crypt32.dll) validates Elliptic Curve Cryptography (ECC) certificates. An attacker could exploit the vulnerability by using a spoofed code-signing certificate to sign a malicious executable, making it appear the file was from a trusted, legitimate source.", "index": [ - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -46,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json new file mode 100644 index 00000000000000..30f4b9883624b0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json @@ -0,0 +1,62 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modifications to the Windows Defender registry settings to disable the service or set the service to be started manually.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Windows Defender Disabled via Registry Modification", + "note": "Detections should be investigated to identify if the hosts and users are authorized to use this tool. As this rule detects post-exploitation process activity, investigations into this should be prioritized", + "query": "registry where event.type in (\"creation\", \"change\") and\n ((registry.path:\"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\DisableAntiSpyware\" and\n registry.data.strings:\"1\") or\n (registry.path:\"HKLM\\\\System\\\\ControlSet*\\\\Services\\\\WinDefend\\\\Start\" and\n registry.data.strings in (\"3\", \"4\")))\n", + "references": [ + "https://thedfirreport.com/2020/12/13/defender-control/" + ], + "risk_score": 21, + "rule_id": "2ffa1f1e-b6db-47fa-994b-1512743847eb", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.006", + "name": "Indicator Blocking", + "reference": "https://attack.mitre.org/techniques/T1562/006/" + }, + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json index cee43c94d97fd2..5630e20e0e65f3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json index 9d61ae658f182e..39c2dc65e6ef7e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json index 86820f7203bfa6..d00a4df394e5c6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json @@ -7,7 +7,8 @@ "index": [ "auditbeat-*", "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json index d4b84879bcf7d6..efd7d43c90c858 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json @@ -2,16 +2,16 @@ "author": [ "Elastic" ], - "description": "Adversaries may attempt to clear the bash command line history in an attempt to evade detection or forensic investigations.", + "description": "Adversaries may attempt to clear or disable the Bash command-line history in an attempt to evade detection or forensic investigations.", "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" ], - "language": "lucene", + "language": "eql", "license": "Elastic License", - "name": "Deletion of Bash Command Line History", - "query": "event.category:process AND event.type:(start or process_started) AND process.name:rm AND process.args:/\\/(home\\/.{1,255}|root)\\/\\.bash_history/", + "name": "Tampering of Bash Command-Line History", + "query": "process where event.type in (\"start\", \"process_started\") and\n (\n (process.args : (\"rm\", \"echo\") and process.args : (\".bash_history\", \"/root/.bash_history\", \"/home/*/.bash_history\")) or\n (process.name : \"history\" and process.args : \"-c\") or\n (process.args : \"export\" and process.args : (\"HISTFILE=/dev/null\", \"HISTFILESIZE=0\")) or\n (process.args : \"unset\" and process.args : \"HISTFILE\") or\n (process.args : \"set\" and process.args : \"history\" and process.args : \"+o\")\n )\n", "risk_score": 47, "rule_id": "7bcbb3ac-e533-41ad-a612-d6c3bf666aba", "severity": "medium", @@ -47,6 +47,6 @@ } ], "timestamp_override": "event.ingested", - "type": "query", - "version": 5 + "type": "eql", + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json index 965b1592f32887..62e785b091915b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json index ed2ee27a2f9e70..49c69d0a83862f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json index 14b92c0eb8f602..fdbb0f0e35a09d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json index cdae07b5e93bb9..fefa5963866a86 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_encoding_or_decoding_files_via_certutil.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json index 21568e4531ec86..b8b0201cb5db7b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json index b0c98c13f5331a..14436f937b4187 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -56,5 +57,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json index 9df293e6cbf3f6..d6976f0c287da9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json index 61f17c8d593df1..f02a8543689b14 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json index 763c17e2792aed..ab2b1ee09c0c14 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json index 6bd60eb4520930..98af8429f28271 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -54,5 +55,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json index c1679e4ce1c63f..07c13af09d8d38 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_via_trusted_developer_utilities.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_via_trusted_developer_utilities.json index 10424863f0290b..d7ea84de18ec87 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_via_trusted_developer_utilities.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_via_trusted_developer_utilities.json @@ -6,9 +6,11 @@ "false_positives": [ "These programs may be used by Windows developers but use by non-engineers is unusual." ], + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -52,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json new file mode 100644 index 00000000000000..55ebb2e428e1e9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "Masquerading can allow an adversary to evade defenses and better blend in with the environment. One way it occurs is when the name or location of a file is manipulated as a means of tricking a user into executing what they think is a benign file type but is actually executable code.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Executable File Creation with Multiple Extensions", + "query": "file where event.type == \"creation\" and file.extension:\"exe\" and\n file.name:\n (\n \"*.vbs.exe\",\n \"*.vbe.exe\",\n \"*.bat.exe\",\n \"*.js.exe\",\n \"*.cmd.exe\",\n \"*.wsh.exe\",\n \"*.ps1.exe\",\n \"*.pdf.exe\",\n \"*.docx.exe\",\n \"*.doc.exe\",\n \"*.xlsx.exe\",\n \"*.xls.exe\",\n \"*.pptx.exe\",\n \"*.ppt.exe\",\n \"*.txt.exe\",\n \"*.rtf.exe\",\n \"*.gif.exe\",\n \"*.jpg.exe\",\n \"*.png.exe\",\n \"*.bmp.exe\",\n \"*.hta.exe\",\n \"*.txt.exe\",\n \"*.img.exe\",\n \"*.iso.exe\"\n )\n", + "risk_score": 47, + "rule_id": "8b2b3a62-a598-4293-bc14-3d5fa22bb98f", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1036", + "name": "Masquerading", + "reference": "https://attack.mitre.org/techniques/T1036/", + "subtechnique": [ + { + "id": "T1036.004", + "name": "Masquerade Task or Service", + "reference": "https://attack.mitre.org/techniques/T1036/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json index e294c84db2d080..3b03c150476135 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json index c2e82ca10a99c4..7e64762ea7cad4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -42,5 +43,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json index 6c416b0a4e0c5f..c85e457823b066 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json @@ -7,7 +7,8 @@ "The Build Engine is commonly used by Windows developers but use by non-engineers is unusual." ], "index": [ - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -57,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json new file mode 100644 index 00000000000000..1548566df04943 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json @@ -0,0 +1,58 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may install a root certificate on a compromised system to avoid warnings when connecting to their command and control servers. Root certificates are used in public key cryptography to identify a root certificate authority (CA). When a root certificate is installed, the system or application will trust certificates in the root's chain of trust that have been signed by the root certificate.", + "false_positives": [ + "Certain applications may install root certificates for the purpose of inspecting SSL traffic." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Attempt to Install Root Certificate", + "query": "event.category:process and event.type:(start or process_started) and process.name:security and process.args:\"add-trusted-cert\"", + "references": [ + "https://ss64.com/osx/security-cert.html" + ], + "risk_score": 47, + "rule_id": "bc1eeacf-2972-434f-b782-3a532b100d67", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1553", + "name": "Subvert Trust Controls", + "reference": "https://attack.mitre.org/techniques/T1553/", + "subtechnique": [ + { + "id": "T1553.004", + "name": "Install Root Certificate", + "reference": "https://attack.mitre.org/techniques/T1553/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json index 0dd2e51995c9be..b71e627fba8130 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json index 95042ffeebd0fc..eb9798a8c5c5f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json index f9b6c3082ef9cc..eb267f2a91b46c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json index 559d81f963abb0..5d344cfaeab606 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -17,7 +18,8 @@ "query": "event.category:process and event.type:(start or process_started) and process.parent.name:WerFault.exe and not process.name:(cofire.exe or psr.exe or VsJITDebugger.exe or TTTracer.exe or rundll32.exe)", "references": [ "https://www.hexacorn.com/blog/2019/09/19/silentprocessexit-quick-look-under-the-hood/", - "https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES/blob/master/Persistence/persistence_SilentProcessExit_ImageHijack_sysmon_13_1.evtx" + "https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES/blob/master/Persistence/persistence_SilentProcessExit_ImageHijack_sysmon_13_1.evtx", + "https://blog.menasec.net/2021/01/" ], "risk_score": 47, "rule_id": "ac5012b8-8da8-440b-aaaf-aedafdea2dff", @@ -48,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json index 4bea1f2f2e6681..c575d2755251be 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Program Files Directory Masquerading", "query": "process where event.type in (\"start\", \"process_started\", \"info\") and\n /* capture both fake program files directory in process executable as well as if passed in process args as a dll*/\n process.args : (\"C:\\\\*Program*Files*\\\\*\", \"C:\\\\*Program*Files*\\\\*\") and\n not process.args : (\"C:\\\\Program Files\\\\*\", \"C:\\\\Program Files (x86)\\\\*\")\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "32c5cf9c-2ef8-4e87-819e-5ccb7cd18b14", "severity": "medium", "tags": [ @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json index 39cf9860dadcdf..0fb020c2bf3742 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json index 686e9a1d4fa492..47b7c3d26f040a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -49,5 +50,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json index 78a22ce98675aa..28b11dc5168b5b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json new file mode 100644 index 00000000000000..a1ed10fc7dbe91 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modifications to an environment variable using the built-in launchctl command. Adversaries may execute their own malicious payloads by hijacking certain environment variables to load arbitrary libraries or bypass certain restrictions.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Modification of Environment Variable via Launchctl", + "query": "event.category:process and event.type:start and process.name:launchctl and process.args:(setenv and not (JAVA*_HOME or RUNTIME_JAVA_HOME or DBUS_LAUNCHD_SESSION_BUS_SOCKET or ANT_HOME))", + "references": [ + "https://github.com/rapid7/metasploit-framework/blob/master//modules/post/osx/escalate/tccbypass.rb" + ], + "risk_score": 47, + "rule_id": "7453e19e-3dbf-4e4e-9ae0-33d6c6ed15e1", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1574", + "name": "Hijack Execution Flow", + "reference": "https://attack.mitre.org/techniques/T1574/", + "subtechnique": [ + { + "id": "T1574.007", + "name": "Path Interception by PATH Environment Variable", + "reference": "https://attack.mitre.org/techniques/T1574/007/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json index b157c7f16f9ac2..361796104a9a7d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_mshta_beacon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_mshta_beacon.json index f3da8be382f469..9947b261c6ef3e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_mshta_beacon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_mshta_beacon.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json index b20766548cb3e6..bed35a87235922 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json index 88fd8a2054ee84..75cf701de1e461 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_port_forwarding_added_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_port_forwarding_added_registry.json index 29e24822e36964..547630e0fa84b2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_port_forwarding_added_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_port_forwarding_added_registry.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_potential_processherpaderping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_potential_processherpaderping.json index 5f8975c41cf183..6ff8025ac0cd52 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_potential_processherpaderping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_potential_processherpaderping.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +44,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privacy_controls_tcc_database_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privacy_controls_tcc_database_modification.json new file mode 100644 index 00000000000000..5f003354393197 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privacy_controls_tcc_database_modification.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the use of sqlite3 to directly modify the Transparency, Consent, and Control (TCC) SQLite database. This may indicate an attempt to bypass macOS privacy controls, including access to sensitive resources like the system camera, microphone, address book, and calendar.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential Privacy Control Bypass via TCCDB Modification", + "query": "process where event.type in (\"start\", \"process_started\") and process.name : \"sqlite*\" and \n process.args : \"/*/Application Support/com.apple.TCC/TCC.db\"\n", + "references": [ + "https://applehelpwriter.com/2016/08/29/discovering-how-dropbox-hacks-your-mac/", + "https://github.com/bp88/JSS-Scripts/blob/master/TCC.db Modifier.sh", + "https://medium.com/@mattshockl/cve-2020-9934-bypassing-the-os-x-transparency-consent-and-control-tcc-framework-for-4e14806f1de8" + ], + "risk_score": 47, + "rule_id": "eea82229-b002-470e-a9e1-00be38b14d32", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json new file mode 100644 index 00000000000000..fad033bd417573 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json @@ -0,0 +1,64 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies use of the Secure Copy Protocol (SCP) to copy files locally by abusing the auto addition of the Secure Shell Daemon (sshd) to the authorized application list for Full Disk Access. This may indicate attempts to bypass macOS privacy controls to access sensitive files.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential Privacy Control Bypass via Localhost Secure Copy", + "query": "process where event.type in (\"start\", \"process_started\") and \n process.name:\"scp\" and\n process.args:\"StrictHostKeyChecking=no\" and \n process.command_line:(\"scp *localhost:/*\", \"scp *127.0.0.1:/*\") and\n not process.args:\"vagrant@*127.0.0.1*\"\n", + "references": [ + "https://blog.trendmicro.com/trendlabs-security-intelligence/xcsset-mac-malware-infects-xcode-projects-performs-uxss-attack-on-safari-other-browsers-leverages-zero-day-exploits/" + ], + "risk_score": 73, + "rule_id": "c02c8b9f-5e1d-463c-a1b0-04edcdfe1a3d", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Privilege Escalation", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_rundll32_no_arguments.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_rundll32_no_arguments.json index 8dbc0e5ef76fef..54938bc2d1d6f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_rundll32_no_arguments.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_rundll32_no_arguments.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json new file mode 100644 index 00000000000000..5a08f3bd90855b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies changes to the Safari configuration using the built-in defaults command. Adversaries may attempt to enable or disable certain Safari settings, such as enabling JavaScript from Apple Events to ease in the hijacking of the users browser.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Modification of Safari Settings via Defaults Command", + "query": "event.category:process and event.type:start and process.name:defaults and process.args: (com.apple.Safari and write and not ( UniversalSearchEnabled or SuppressSearchSuggestions or WebKitTabToLinksPreferenceKey or ShowFullURLInSmartSearchField or com.apple.Safari.ContentPageGroupIdentifier.WebKit2TabsToLinks ) )", + "references": [ + "https://objectivebythesea.com/v2/talks/OBTS_v2_Zohar.pdf" + ], + "risk_score": 47, + "rule_id": "6482255d-f468-45ea-a5b3-d3a7de1331ae", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json new file mode 100644 index 00000000000000..9542eed5a6060b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json @@ -0,0 +1,33 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation of a suspicious zip file prepended with special characters. Sandboxed Microsoft Office applications on macOS are allowed to write files that start with special characters, which can be combined with an AutoStart location to achieve sandbox evasion.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Microsoft Office Sandbox Evasion", + "query": "event.category:file and not event.type:deletion and file.name:~$*.zip", + "references": [ + "https://i.blackhat.com/USA-20/Wednesday/us-20-Wardle-Office-Drama-On-macOS.pdf", + "https://www.mdsec.co.uk/2018/08/escaping-the-sandbox-microsoft-office-on-macos/", + "https://desi-jarvis.medium.com/office365-macos-sandbox-escape-fcce4fa4123c" + ], + "risk_score": 73, + "rule_id": "d22a85c6-d2ad-4cc4-bf7b-54787473669a", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json index 0176016d89f4de..e6cb50ab79f4b7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json index eb01d608e54b75..a67e09916b783c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Potential Secure File Deletion via SDelete Utility", "note": "Verify process details such as command line and hash to confirm this activity legitimacy.", - "query": "file where event.type == \"change\" and wildcard(file.name,\"*AAA.AAA\")\n", + "query": "file where event.type == \"change\" and file.name : \"*AAA.AAA\"\n", "risk_score": 21, "rule_id": "5aee924b-6ceb-4633-980e-1bde8cdb40c5", "severity": "low", @@ -49,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json new file mode 100644 index 00000000000000..4eea43aada3a8c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modifications to the registered Subject Interface Package (SIP) providers. SIP providers are used by the Windows cryptographic system to validate file signatures on the system. This may be an attempt to bypass signature validation checks or inject code into critical processes.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "SIP Provider Modification", + "query": "registry where event.type:\"change\" and\n registry.path: (\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Cryptography\\\\OID\\\\EncodingType 0\\\\CryptSIPDllPutSignedDataMsg\\\\{*}\\\\Dll\",\n \"HKLM\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Cryptography\\\\OID\\\\EncodingType 0\\\\CryptSIPDllPutSignedDataMsg\\\\{*}\\\\Dll\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Cryptography\\\\Providers\\\\Trust\\\\FinalPolicy\\\\{*}\\\\$Dll\",\n \"HKLM\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Cryptography\\\\Providers\\\\Trust\\\\FinalPolicy\\\\{*}\\\\$Dll\"\n ) and\n registry.data.strings:\"*.dll\"\n", + "references": [ + "https://github.com/mattifestation/PoCSubjectInterfacePackage" + ], + "risk_score": 47, + "rule_id": "f2c7b914-eda3-40c2-96ac-d23ef91776ca", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1553", + "name": "Subvert Trust Controls", + "reference": "https://attack.mitre.org/techniques/T1553/", + "subtechnique": [ + { + "id": "T1553.003", + "name": "SIP and Trust Provider Hijacking", + "reference": "https://attack.mitre.org/techniques/T1553/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json index 0b5ca834c9c8f9..f9a94e85f5a470 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -73,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json index 4ee3172fea7ebc..d5df975cfbd871 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -51,5 +52,5 @@ "value": 10 }, "type": "threshold", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json index f6722e13eaf056..fbd006936253ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json index 2f0f762031faa5..4f464327706609 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Windows Suspicious Script Object Execution", - "query": "/* add winlogbeat-* when process.code_signature.* fields are populated */\n\nsequence by process.entity_id with maxspan=2m\n [process where event.type in (\"start\", \"process_started\") and\n /* uncomment once in winlogbeat */\n /* process.code_signature.subject_name == \"Microsoft Corporation\" and process.code_signature.trusted == true and */\n not (process.name : \"cscript.exe\" or\n process.name : \"iexplore.exe\" or\n process.name : \"MicrosoftEdge.exe\" or\n process.name : \"msiexec.exe\" or\n process.name : \"smartscreen.exe\" or\n process.name : \"taskhostw.exe\" or\n process.name : \"w3wp.exe\" or\n process.name : \"wscript.exe\")]\n [library where event.type == \"start\" and file.name : \"scrobj.dll\"]\n", + "query": "/* add winlogbeat-* when process.code_signature.* fields are populated */\n\nsequence by process.entity_id with maxspan = 2m\n [process where event.type in (\"start\", \"process_started\") and\n /* uncomment once in winlogbeat */\n /* process.code_signature.subject_name : \"Microsoft Corporation\" and process.code_signature.trusted : true and */\n not process.name : (\n \"cscript.exe\",\n \"iexplore.exe\",\n \"MicrosoftEdge.exe\",\n \"msiexec.exe\",\n \"smartscreen.exe\",\n \"taskhostw.exe\",\n \"w3wp.exe\",\n \"wscript.exe\")]\n [library where event.type == \"start\" and dll.name : \"scrobj.dll\"]\n", "risk_score": 21, "rule_id": "4ed678a9-3a4f-41fb-9fea-f85a6e0a0dff", "severity": "medium", @@ -34,5 +35,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json index e9224162643595..7c6998f929e090 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json @@ -5,12 +5,14 @@ "description": "Identifies WMIC whitelisting bypass techniques by alerting on suspicious execution of scripts. When WMIC loads scripting libraries it may be indicative of a whitelist bypass.", "from": "now-9m", "index": [ - "logs-endpoint.events.*" + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious WMIC XSL Script Execution", - "query": "sequence by process.entity_id with maxspan=2m\n[process where event.type in (\"start\", \"process_started\") and\n (process.name : \"WMIC.exe\" or process.pe.original_file_name == \"wmic.exe\") and\n wildcard(process.args, \"format*:*\", \"/format*:*\", \"*-format*:*\") and\n not wildcard(process.command_line, \"* /format:table *\")]\n[library where event.type == \"start\" and file.name in (\"jscript.dll\", \"vbscript.dll\")]\n", + "query": "sequence by process.entity_id with maxspan = 2m\n[process where event.type in (\"start\", \"process_started\") and\n (process.name : \"WMIC.exe\" or process.pe.original_file_name : \"wmic.exe\") and\n process.args : (\"format*:*\", \"/format*:*\", \"*-format*:*\") and\n not process.command_line : \"* /format:table *\"]\n[library where event.type == \"start\" and dll.name : (\"jscript.dll\", \"vbscript.dll\")]\n", "risk_score": 21, "rule_id": "7f370d54-c0eb-4270-ac5a-9a6020585dc6", "severity": "medium", @@ -39,5 +41,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json index 6abe5b5ee4c400..571f42cf0e942a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -46,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json index 265f648c7959dc..811dae6b7ce534 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json new file mode 100644 index 00000000000000..d79c90e703ef73 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json @@ -0,0 +1,48 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the use of the mount_apfs command to mount the entire file system through Apple File System (APFS) snapshots as read-only and with the noowners flag set. This action enables the adversary to access almost any file in the file system, including all user data and files protected by Apple\u2019s privacy framework (TCC).", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "TCC Bypass via Mounted APFS Snapshot Access", + "query": "event.category : process and event.type : (start or process_started) and process.name : mount_apfs and process.args : (/System/Volumes/Data and noowners)", + "references": [ + "https://theevilbit.github.io/posts/cve_2020_9771/" + ], + "risk_score": 73, + "rule_id": "b00bcd89-000c-4425-b94c-716ef67762f6", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1006", + "name": "Direct Volume Access", + "reference": "https://attack.mitre.org/techniques/T1006/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_timestomp_touch.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_timestomp_touch.json index fe8268d11cc2d7..e6275668be487c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_timestomp_touch.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_timestomp_touch.json @@ -12,7 +12,7 @@ "license": "Elastic License", "max_signals": 33, "name": "Timestomping using Touch Command", - "query": "process where event.type in (\"start\", \"process_started\") and\n process.name == \"touch\" and wildcard(process.args, \"-r\", \"-t\", \"-a*\",\"-m*\")\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.name : \"touch\" and process.args : (\"-r\", \"-t\", \"-a*\",\"-m*\")\n", "risk_score": 47, "rule_id": "b0046934-486e-462f-9487-0d4cf9e429c6", "severity": "medium", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json new file mode 100644 index 00000000000000..9725619d5b3996 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json @@ -0,0 +1,52 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to unload the Elastic Endpoint Security kernel extension via the kextunload command.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Attempt to Unload Elastic Endpoint Security Kernel Extension", + "query": "event.category:process and event.type:(start or process_started) and process.name:kextunload and process.args:(\"/System/Library/Extensions/EndpointSecurity.kext\" or \"EndpointSecurity.kext\")", + "risk_score": 73, + "rule_id": "70fa1af4-27fd-4f26-bd03-50b6af6b9e24", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json new file mode 100644 index 00000000000000..4afbf0472f0258 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies suspicious creation of Alternate Data Streams on highly targeted files. This is uncommon for legitimate files and sometimes done by adversaries to hide malware.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Unusual File Creation - Alternate Data Stream", + "query": "file where event.type == \"creation\" and\n file.path : \"C:\\\\*:*\" and\n not file.path : \"C:\\\\*:zone.identifier*\" and\n file.extension :\n (\n \"pdf\",\n \"dll\",\n \"png\",\n \"exe\",\n \"dat\",\n \"com\",\n \"bat\",\n \"cmd\",\n \"sys\",\n \"vbs\",\n \"ps1\",\n \"hta\",\n \"txt\",\n \"vbe\",\n \"js\",\n \"wsh\",\n \"docx\",\n \"doc\",\n \"xlsx\",\n \"xls\",\n \"pptx\",\n \"ppt\",\n \"rtf\",\n \"gif\",\n \"jpg\",\n \"png\",\n \"bmp\",\n \"img\",\n \"iso\"\n )\n", + "risk_score": 47, + "rule_id": "71bccb61-e19b-452f-b104-79a60e546a95", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1564", + "name": "Hide Artifacts", + "reference": "https://attack.mitre.org/techniques/T1564/", + "subtechnique": [ + { + "id": "T1564.004", + "name": "NTFS File Attributes", + "reference": "https://attack.mitre.org/techniques/T1564/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json index 3327afd89f541e..ed10ddf4a4fb7e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies processes running from an Alternate Data Stream. This is uncommon for legitimate processes and sometimes done by adversaries to hide malware.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json index 8c885aa52be593..19665527ccabb6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json index bd8ac27521a439..77b932b97c88f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json index 819c85a1ade628..f20998dea964ba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json index fc7a66be016ef4..797a2bad31c4c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "The Filter Manager Control Program (fltMC.exe) binary may be abused by adversaries to unload a filter driver and evade defenses.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json index b41ad15ca3e17d..11623d61fa6972 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json index 6bdc8a65154e31..64483156611776 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -74,5 +75,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json index 57c9ba77385c11..355b5df097a13f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies instances of lower privilege accounts enumerating Administrator accounts or groups using built-in Windows tools.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -45,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json index c6ed7328701953..a4e76120504cc7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json @@ -3,14 +3,19 @@ "Elastic" ], "description": "Enumeration of files and directories using built-in tools. Adversaries may use the information discovered to plan follow-on activity.", + "false_positives": [ + "Enumeration of files and directories may not be inherently malicious and noise may come from scripts, automation tools, or normal command line usage. It's important to baseline your environment to determine the amount of expected noise and exclude any known FP's from the rule." + ], + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "File and Directory Discovery", - "query": "process where event.type in (\"start\", \"process_started\") and\n (process.name : \"cmd.exe\" or process.pe.original_file_name == \"Cmd.Exe\") and\n process.args : (\"dir\", \"tree\")\n", + "query": "sequence by agent.id, user.name with maxspan=1m\n[process where event.type in (\"start\", \"process_started\") and\n ((process.name : \"cmd.exe\" or process.pe.original_file_name == \"Cmd.Exe\") and process.args : \"dir\") or\n process.name : \"tree.com\"]\n[process where event.type in (\"start\", \"process_started\") and\n ((process.name : \"cmd.exe\" or process.pe.original_file_name == \"Cmd.Exe\") and process.args : \"dir\") or\n process.name : \"tree.com\"]\n[process where event.type in (\"start\", \"process_started\") and\n ((process.name : \"cmd.exe\" or process.pe.original_file_name == \"Cmd.Exe\") and process.args : \"dir\") or\n process.name : \"tree.com\"]\n", "risk_score": 21, "rule_id": "7b08314d-47a0-4b71-ae4e-16544176924f", "severity": "low", @@ -40,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json index 5572178361a09d..25cdc2bd1c44ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json index 0f4be41389cbca..1cb6364a323f57 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies attempts to enumerate hosts in a network using the built-in Windows net.exe tool.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -45,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json index 005a7d34ba7183..467ce488252a1e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies use of the Windows file system utility (fsutil.exe ) to gather information about attached peripheral devices and components connected to a computer system.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_process_discovery_via_tasklist_command.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_process_discovery_via_tasklist_command.json index af28fe75f525f6..32a434c1a9a062 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_process_discovery_via_tasklist_command.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_process_discovery_via_tasklist_command.json @@ -6,9 +6,11 @@ "false_positives": [ "Administrators may use the tasklist command to display a list of currently running processes. By itself, it does not indicate malicious activity. After obtaining a foothold, it's possible adversaries may use discovery commands like tasklist to get information about running processes." ], + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -43,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_query_registry_via_reg.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_query_registry_via_reg.json index a04e97e2fa9f6f..1933ec66c866f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_query_registry_via_reg.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_query_registry_via_reg.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Enumeration or discovery of the Windows registry using reg.exe. This information can be used to perform follow-on activities.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json index 24e3bd87c526dd..8f8ccf5943061f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Discovery of remote system information using built-in commands, which may be used to mover laterally.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json new file mode 100644 index 00000000000000..d211fad332da67 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json @@ -0,0 +1,46 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the use of the grep command to discover known third-party macOS and Linux security tools, such as Antivirus or Host Firewall details.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*", + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Security Software Discovery via Grep", + "query": "event.category : process and event.type : (start or process_started) and process.name : grep and process.args : (\"Little Snitch\" or Avast* or Avira* or ESET* or esets_* or BlockBlock or 360* or LuLu or KnockKnock* or kav or KIS or RTProtectionDaemon or Malware* or VShieldScanner or WebProtection or webinspectord or McAfee* or isecespd* or macmnsvc* or masvc or kesl or avscan or guard or rtvscand or symcfgd or scmdaemon or symantec or elastic-endpoint )", + "risk_score": 47, + "rule_id": "870aecc0-cea4-4110-af3f-e02e9b373655", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Linux", + "Threat Detection", + "Discovery" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0007", + "name": "Discovery", + "reference": "https://attack.mitre.org/tactics/TA0007/" + }, + "technique": [ + { + "id": "T1518", + "name": "Software Discovery", + "reference": "https://attack.mitre.org/techniques/T1518/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json index 51d4425f7d5382..89c6390856e926 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_users_domain_built_in_commands.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_users_domain_built_in_commands.json new file mode 100644 index 00000000000000..928585fb59968d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_users_domain_built_in_commands.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of macOS built-in commands related to account or group enumeration.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Enumeration of Users or Groups via Built-in Commands", + "query": "process where event.type in (\"start\", \"process_started\") and\n not process.parent.executable : (\"/Applications/NoMAD.app/Contents/MacOS/NoMAD\", \n \"/Applications/ZoomPresence.app/Contents/MacOS/ZoomPresence\") and \n process.name : (\"ldapsearch\", \"dsmemberutil\") or\n (process.name : \"dscl\" and \n process.args : (\"read\", \"-read\", \"list\", \"-list\", \"ls\", \"search\", \"-search\") and \n process.args : (\"/Active Directory/*\", \"/Users*\", \"/Groups*\"))\n", + "risk_score": 21, + "rule_id": "6e9b351e-a531-4bdc-b73e-7034d6eed7ff", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Discovery" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0007", + "name": "Discovery", + "reference": "https://attack.mitre.org/tactics/TA0007/" + }, + "technique": [ + { + "id": "T1069", + "name": "Permission Groups Discovery", + "reference": "https://attack.mitre.org/techniques/T1069/" + }, + { + "id": "T1087", + "name": "Account Discovery", + "reference": "https://attack.mitre.org/techniques/T1087/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json index bb6810f5437d32..2ed21b39f8246e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json @@ -6,9 +6,11 @@ "false_positives": [ "Some normal use of this program, at varying levels of frequency, may originate from scripts, automation tools and frameworks. Usage by non-engineers and ordinary users is unusual." ], + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -43,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json index 8786510ee5cb79..a60122ef6c1a29 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -70,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json index fc12a48e3f5a16..04f204a4596b35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -70,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_com_object_xwizard.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_com_object_xwizard.json new file mode 100644 index 00000000000000..182704d149e48e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_com_object_xwizard.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Windows Component Object Model (COM) is an inter-process communication (IPC) component of the native Windows application programming interface (API) that enables interaction between software objects or executable code. Xwizard can be used to run a COM object created in registry to evade defensive counter measures.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Execution of COM object via Xwizard", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.pe.original_file_name : \"xwizard.exe\" and\n (\n (process.args : \"RunWizard\" and process.args : \"{*}\") or\n (process.executable != null and\n not process.executable : (\"C:\\\\Windows\\\\SysWOW64\\\\xwizard.exe\", \"C:\\\\Windows\\\\System32\\\\xwizard.exe\")\n )\n )\n", + "references": [ + "https://lolbas-project.github.io/lolbas/Binaries/Xwizard/", + "http://www.hexacorn.com/blog/2017/07/31/the-wizard-of-x-oppa-plugx-style/" + ], + "risk_score": 47, + "rule_id": "1a6075b0-7479-450e-8fe7-b8b8438ac570", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1559", + "name": "Inter-Process Communication", + "reference": "https://attack.mitre.org/techniques/T1559/", + "subtechnique": [ + { + "id": "T1559.001", + "name": "Component Object Model", + "reference": "https://attack.mitre.org/techniques/T1559/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json index 12d2a94afc823a..e038ed56ec1759 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -58,5 +59,5 @@ } ], "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json index aa0632c5614f6e..16b964b6e95888 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_powershell.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json index d5042ee5d64fd3..cde62236effe03 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_unusual_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_unusual_process.json index 90b3759d93de9f..981fdb5a4f7f20 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_unusual_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_unusual_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_via_rundll32.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_via_rundll32.json index 45ee672c1d635c..641abc92b1204f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_via_rundll32.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_via_rundll32.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json new file mode 100644 index 00000000000000..eb80782fe2495b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json @@ -0,0 +1,66 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to execute a child process from within the context of an Electron application using the child_process Node.js module. Adversaries may abuse this technique to inherit permissions from parent processes.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Execution via Electron Child Process Node.js Module", + "query": "event.category:process and event.type:(start or process_started) and process.args:(\"-e\" and const*require*child_process*)", + "references": [ + "https://www.matthewslipper.com/2019/09/22/everything-you-wanted-electron-child-process.html", + "https://www.trustedsec.com/blog/macos-injection-via-third-party-frameworks/", + "https://nodejs.org/api/child_process.html" + ], + "risk_score": 47, + "rule_id": "35330ba2-c859-4c98-8b7f-c19159ea0e58", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Defense Evasion", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json new file mode 100644 index 00000000000000..0fe7321ebe4b36 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json @@ -0,0 +1,46 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies native Windows host and network enumeration commands spawned by the Windows Management Instrumentation Provider Service (WMIPrvSE).", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Enumeration Command Spawned via WMIPrvSE", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.name:\n (\n \"arp.exe\",\n \"dsquery.exe\",\n \"dsget.exe\",\n \"gpresult.exe\",\n \"hostname.exe\",\n \"ipconfig.exe\",\n \"nbtstat.exe\",\n \"net.exe\",\n \"net1.exe\",\n \"netsh.exe\",\n \"netstat.exe\",\n \"nltest.exe\",\n \"ping.exe\",\n \"qprocess.exe\",\n \"quser.exe\",\n \"qwinsta.exe\",\n \"reg.exe\",\n \"sc.exe\",\n \"systeminfo.exe\",\n \"tasklist.exe\",\n \"tracert.exe\",\n \"whoami.exe\"\n ) and\n process.parent.name:\"wmiprvse.exe\"\n", + "risk_score": 21, + "rule_id": "770e0c4d-b998-41e5-a62e-c7901fd7f470", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1047", + "name": "Windows Management Instrumentation", + "reference": "https://attack.mitre.org/techniques/T1047/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_directory.json index 43166722e6fc06..a6bc33e4b2e735 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_directory.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_directory.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -24,5 +25,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json index 2663b97bd91514..63adf0a602e2c4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Execution from Unusual Directory - Command Line", "note": "This is related to the Process Execution from an Unusual Directory rule", - "query": "process where event.type in (\"start\", \"process_started\", \"info\") and\n process.name : (\"wscript.exe\",\"cscript.exe\",\"rundll32.exe\",\"regsvr32.exe\",\"cmstp.exe\",\"RegAsm.exe\",\"installutil.exe\",\"mshta.exe\",\"RegSvcs.exe\") and\n /* add suspicious execution paths here */\nprocess.args : (\"C:\\\\PerfLogs\\\\*\",\"C:\\\\Users\\\\Public\\\\*\",\"C:\\\\Users\\\\Default\\\\*\",\"C:\\\\Windows\\\\Tasks\\\\*\",\"C:\\\\Intel\\\\*\", \"C:\\\\AMD\\\\Temp\\\\*\", \n \"C:\\\\Windows\\\\AppReadiness\\\\*\", \"C:\\\\Windows\\\\ServiceState\\\\*\",\"C:\\\\Windows\\\\security\\\\*\",\"C:\\\\Windows\\\\IdentityCRL\\\\*\",\"C:\\\\Windows\\\\Branding\\\\*\",\"C:\\\\Windows\\\\csc\\\\*\",\n \"C:\\\\Windows\\\\DigitalLocker\\\\*\",\"C:\\\\Windows\\\\en-US\\\\*\",\"C:\\\\Windows\\\\wlansvc\\\\*\",\"C:\\\\Windows\\\\Prefetch\\\\*\",\"C:\\\\Windows\\\\Fonts\\\\*\",\n \"C:\\\\Windows\\\\diagnostics\\\\*\",\"C:\\\\Windows\\\\TAPI\\\\*\",\"C:\\\\Windows\\\\INF\\\\*\",\"C:\\\\Windows\\\\System32\\\\Speech\\\\*\",\"C:\\\\windows\\\\tracing\\\\*\",\n \"c:\\\\windows\\\\IME\\\\*\",\"c:\\\\Windows\\\\Performance\\\\*\",\"c:\\\\windows\\\\intel\\\\*\",\"c:\\\\windows\\\\ms\\\\*\",\"C:\\\\Windows\\\\dot3svc\\\\*\",\"C:\\\\Windows\\\\ServiceProfiles\\\\*\",\n \"C:\\\\Windows\\\\panther\\\\*\",\"C:\\\\Windows\\\\RemotePackages\\\\*\",\"C:\\\\Windows\\\\OCR\\\\*\",\"C:\\\\Windows\\\\appcompat\\\\*\",\"C:\\\\Windows\\\\apppatch\\\\*\",\"C:\\\\Windows\\\\addins\\\\*\",\n \"C:\\\\Windows\\\\Setup\\\\*\",\"C:\\\\Windows\\\\Help\\\\*\",\"C:\\\\Windows\\\\SKB\\\\*\",\"C:\\\\Windows\\\\Vss\\\\*\",\"C:\\\\Windows\\\\Web\\\\*\",\"C:\\\\Windows\\\\servicing\\\\*\",\"C:\\\\Windows\\\\CbsTemp\\\\*\",\n \"C:\\\\Windows\\\\Logs\\\\*\",\"C:\\\\Windows\\\\WaaS\\\\*\",\"C:\\\\Windows\\\\twain_32\\\\*\",\"C:\\\\Windows\\\\ShellExperiences\\\\*\",\"C:\\\\Windows\\\\ShellComponents\\\\*\",\"C:\\\\Windows\\\\PLA\\\\*\",\n \"C:\\\\Windows\\\\Migration\\\\*\",\"C:\\\\Windows\\\\debug\\\\*\",\"C:\\\\Windows\\\\Cursors\\\\*\",\"C:\\\\Windows\\\\Containers\\\\*\",\"C:\\\\Windows\\\\Boot\\\\*\",\"C:\\\\Windows\\\\bcastdvr\\\\*\",\n \"C:\\\\Windows\\\\assembly\\\\*\",\"C:\\\\Windows\\\\TextInput\\\\*\",\"C:\\\\Windows\\\\security\\\\*\",\"C:\\\\Windows\\\\schemas\\\\*\",\"C:\\\\Windows\\\\SchCache\\\\*\",\"C:\\\\Windows\\\\Resources\\\\*\",\n \"C:\\\\Windows\\\\rescache\\\\*\",\"C:\\\\Windows\\\\Provisioning\\\\*\",\"C:\\\\Windows\\\\PrintDialog\\\\*\",\"C:\\\\Windows\\\\PolicyDefinitions\\\\*\",\"C:\\\\Windows\\\\media\\\\*\",\n \"C:\\\\Windows\\\\Globalization\\\\*\",\"C:\\\\Windows\\\\L2Schemas\\\\*\",\"C:\\\\Windows\\\\LiveKernelReports\\\\*\",\"C:\\\\Windows\\\\ModemLogs\\\\*\",\"C:\\\\Windows\\\\ImmersiveControlPanel\\\\*\")\n", + "query": "process where event.type in (\"start\", \"process_started\", \"info\") and\n process.name : (\"wscript.exe\",\"cscript.exe\",\"rundll32.exe\",\"regsvr32.exe\",\"cmstp.exe\",\"RegAsm.exe\",\"installutil.exe\",\"mshta.exe\",\"RegSvcs.exe\", \"powershell.exe\", \"pwsh.exe\", \"cmd.exe\") and\n /* add suspicious execution paths here */\nprocess.args : (\"C:\\\\PerfLogs\\\\*\",\"C:\\\\Users\\\\Public\\\\*\",\"C:\\\\Users\\\\Default\\\\*\",\"C:\\\\Windows\\\\Tasks\\\\*\",\"C:\\\\Intel\\\\*\", \"C:\\\\AMD\\\\Temp\\\\*\", \n \"C:\\\\Windows\\\\AppReadiness\\\\*\", \"C:\\\\Windows\\\\ServiceState\\\\*\",\"C:\\\\Windows\\\\security\\\\*\",\"C:\\\\Windows\\\\IdentityCRL\\\\*\",\"C:\\\\Windows\\\\Branding\\\\*\",\"C:\\\\Windows\\\\csc\\\\*\",\n \"C:\\\\Windows\\\\DigitalLocker\\\\*\",\"C:\\\\Windows\\\\en-US\\\\*\",\"C:\\\\Windows\\\\wlansvc\\\\*\",\"C:\\\\Windows\\\\Prefetch\\\\*\",\"C:\\\\Windows\\\\Fonts\\\\*\",\n \"C:\\\\Windows\\\\diagnostics\\\\*\",\"C:\\\\Windows\\\\TAPI\\\\*\",\"C:\\\\Windows\\\\INF\\\\*\",\"C:\\\\Windows\\\\System32\\\\Speech\\\\*\",\"C:\\\\windows\\\\tracing\\\\*\",\n \"c:\\\\windows\\\\IME\\\\*\",\"c:\\\\Windows\\\\Performance\\\\*\",\"c:\\\\windows\\\\intel\\\\*\",\"c:\\\\windows\\\\ms\\\\*\",\"C:\\\\Windows\\\\dot3svc\\\\*\",\"C:\\\\Windows\\\\ServiceProfiles\\\\*\",\n \"C:\\\\Windows\\\\panther\\\\*\",\"C:\\\\Windows\\\\RemotePackages\\\\*\",\"C:\\\\Windows\\\\OCR\\\\*\",\"C:\\\\Windows\\\\appcompat\\\\*\",\"C:\\\\Windows\\\\apppatch\\\\*\",\"C:\\\\Windows\\\\addins\\\\*\",\n \"C:\\\\Windows\\\\Setup\\\\*\",\"C:\\\\Windows\\\\Help\\\\*\",\"C:\\\\Windows\\\\SKB\\\\*\",\"C:\\\\Windows\\\\Vss\\\\*\",\"C:\\\\Windows\\\\Web\\\\*\",\"C:\\\\Windows\\\\servicing\\\\*\",\"C:\\\\Windows\\\\CbsTemp\\\\*\",\n \"C:\\\\Windows\\\\Logs\\\\*\",\"C:\\\\Windows\\\\WaaS\\\\*\",\"C:\\\\Windows\\\\twain_32\\\\*\",\"C:\\\\Windows\\\\ShellExperiences\\\\*\",\"C:\\\\Windows\\\\ShellComponents\\\\*\",\"C:\\\\Windows\\\\PLA\\\\*\",\n \"C:\\\\Windows\\\\Migration\\\\*\",\"C:\\\\Windows\\\\debug\\\\*\",\"C:\\\\Windows\\\\Cursors\\\\*\",\"C:\\\\Windows\\\\Containers\\\\*\",\"C:\\\\Windows\\\\Boot\\\\*\",\"C:\\\\Windows\\\\bcastdvr\\\\*\",\n \"C:\\\\Windows\\\\assembly\\\\*\",\"C:\\\\Windows\\\\TextInput\\\\*\",\"C:\\\\Windows\\\\security\\\\*\",\"C:\\\\Windows\\\\schemas\\\\*\",\"C:\\\\Windows\\\\SchCache\\\\*\",\"C:\\\\Windows\\\\Resources\\\\*\",\n \"C:\\\\Windows\\\\rescache\\\\*\",\"C:\\\\Windows\\\\Provisioning\\\\*\",\"C:\\\\Windows\\\\PrintDialog\\\\*\",\"C:\\\\Windows\\\\PolicyDefinitions\\\\*\",\"C:\\\\Windows\\\\media\\\\*\",\n \"C:\\\\Windows\\\\Globalization\\\\*\",\"C:\\\\Windows\\\\L2Schemas\\\\*\",\"C:\\\\Windows\\\\LiveKernelReports\\\\*\",\"C:\\\\Windows\\\\ModemLogs\\\\*\",\"C:\\\\Windows\\\\ImmersiveControlPanel\\\\*\",\n \"C:\\\\$Recycle.Bin\\\\*\")\n", "risk_score": 47, "rule_id": "cff92c41-2225-4763-b4ce-6f71e5bda5e6", "severity": "medium", @@ -25,5 +26,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json index b85e74c8546368..9cf6ee67a06ccc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -56,5 +57,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_initial_access_suspicious_browser_childproc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_initial_access_suspicious_browser_childproc.json new file mode 100644 index 00000000000000..61e770b25290d0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_initial_access_suspicious_browser_childproc.json @@ -0,0 +1,64 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a suspicious browser child process. Adversaries may gain access to a system through a user visiting a website over the normal course of browsing. With this technique, the user's web browser is typically targeted for exploitation.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious Browser Child Process", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : (\"Google Chrome\", \"Google Chrome Helper*\", \"firefox\", \"Opera\", \"Safari\", \"com.apple.WebKit.WebContent\", \"Microsoft Edge\") and\n process.name : (\"sh\", \"bash\", \"dash\", \"ksh\", \"tcsh\", \"zsh\", \"curl\", \"wget\", \"python*\", \"perl*\", \"php*\", \"osascript\", \"pwsh\") and \n process.command_line != null and \n not process.args : \n ( \n \"/Library/Application Support/Microsoft/MAU*/Microsoft AutoUpdate.app/Contents/MacOS/msupdate\", \n \"hw.model\", \n \"IOPlatformExpertDevice\", \n \"/Volumes/Google Chrome/Google Chrome.app/Contents/Frameworks/*/Resources/install.sh\",\n \"--defaults-torrc\", \n \"Chrome.app\", \n \"Framework.framework/Versions/*/Resources/keystone_promote_preflight.sh\", \n \"/Users/*/Library/Application Support/Google/Chrome/recovery/*/ChromeRecovery\", \n \"$DISPLAY\", \n \"GIO_LAUNCHED_DESKTOP_FILE_PID=$$\"\n )\n", + "references": [ + "https://objective-see.com/blog/blog_0x43.html", + "https://fr.slideshare.net/codeblue_jp/cb19-recent-apt-attack-on-crypto-exchange-employees-by-heungsoo-kang" + ], + "risk_score": 73, + "rule_id": "080bc66a-5d56-4d1f-8071-817671716db9", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Initial Access", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1203", + "name": "Exploitation for Client Execution", + "reference": "https://attack.mitre.org/techniques/T1203/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1189", + "name": "Drive-by Compromise", + "reference": "https://attack.mitre.org/techniques/T1189/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json index cf1fbc4ba0ba2a..dee13e29dfebcb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -61,5 +62,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json index f0d32cf8882bba..a49e225777ca77 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -61,5 +62,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json new file mode 100644 index 00000000000000..b86a5c1d25bdd2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json @@ -0,0 +1,32 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of and EggShell Backdoor. EggShell is a known post exploitation tool for macOS and Linux.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "EggShell Backdoor Execution", + "query": "event.category:process and event.type:(start or process_started) and process.name:espl and process.args:eyJkZWJ1ZyI6*", + "references": [ + "https://github.com/neoneggplant/EggShell" + ], + "risk_score": 73, + "rule_id": "41824afb-d68c-4d0e-bfee-474dac1fa56e", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Execution" + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json index 00a63dded94c6a..ef219d1aa6c75a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -59,5 +60,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json index 4a5defb4f42a41..21672969ccf8fb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -59,5 +60,5 @@ } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json new file mode 100644 index 00000000000000..12a8a27bb65d5f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json @@ -0,0 +1,51 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a shell process with suspicious arguments which may be indicative of reverse shell activity.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential Reverse Shell Activity via Terminal", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.name in (\"sh\", \"bash\", \"zsh\", \"dash\", \"zmodload\") and\n process.args:(\"*/dev/tcp/*\", \"*/dev/udp/*\", \"zsh/net/tcp\", \"zsh/net/udp\")\n", + "references": [ + "https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Reverse%20Shell%20Cheatsheet.md", + "https://github.com/WangYihang/Reverse-Shell-Manager", + "https://www.netsparker.com/blog/web-security/understanding-reverse-shells/" + ], + "risk_score": 73, + "rule_id": "a1a0375f-22c2-48c0-81a4-7c2d11cc6856", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json index 3c7e0d00be9078..9ace0fe4e2f8e5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json @@ -9,12 +9,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Outbound Scheduled Task Activity via PowerShell", - "query": "sequence by host.id, process.entity_id with maxspan = 5s\n [library where file.name: \"taskschd.dll\" and process.name: (\"powershell.exe\", \"pwsh.exe\")]\n [network where process.name : (\"powershell.exe\", \"pwsh.exe\") and destination.port == 135 and not destination.address in (\"127.0.0.1\", \"::1\")]\n", + "query": "sequence by host.id, process.entity_id with maxspan = 5s\n [library where dll.name : \"taskschd.dll\" and process.name : (\"powershell.exe\", \"pwsh.exe\")]\n [network where process.name : (\"powershell.exe\", \"pwsh.exe\") and destination.port == 135 and not destination.address in (\"127.0.0.1\", \"::1\")]\n", "references": [ "https://www.volexity.com/blog/2020/12/14/dark-halo-leverages-solarwinds-compromise-to-breach-organizations/" ], @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_via_automator_workflows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_via_automator_workflows.json new file mode 100644 index 00000000000000..8796a3edcc3459 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_via_automator_workflows.json @@ -0,0 +1,47 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of the Automator Workflows process followed by a network connection from it's XPC service. Adversaries may drop a custom workflow template that hosts malicious JavaScript for Automation (JXA) code as an alternative to using osascript.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious Automator Workflows Execution", + "query": "sequence by host.id with maxspan=30s\n [process where event.type in (\"start\", \"process_started\") and process.name == \"automator\"]\n [network where process.name:\"com.apple.automator.runner\"]\n", + "references": [ + "https://posts.specterops.io/persistent-jxa-66e1c3cd1cf5" + ], + "risk_score": 47, + "rule_id": "5d9f8cfc-0d03-443e-a167-2b0597ce0965", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/" + } + ] + } + ], + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shared_modules_local_sxs_dll.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shared_modules_local_sxs_dll.json index 98a11faa076d4f..d3d8715d29be09 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shared_modules_local_sxs_dll.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shared_modules_local_sxs_dll.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -16,7 +17,7 @@ "references": [ "https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-redirection" ], - "risk_score": 43, + "risk_score": 47, "rule_id": "a3ea12f3-0d4e-4667-8b44-4230c63f3c75", "severity": "medium", "tags": [ @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_cmd_wmi.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_cmd_wmi.json index 7c4480b5e9c574..8f6e2d6eca71b8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_cmd_wmi.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_cmd_wmi.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious Cmd Execution via WMI", - "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name == \"WmiPrvSE.exe\" and process.name == \"cmd.exe\" and\n wildcard(process.args, \"\\\\\\\\127.0.0.1\\\\*\") and process.args in (\"2>&1\", \"1>\")\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"WmiPrvSE.exe\" and process.name : \"cmd.exe\" and\n process.args : \"\\\\\\\\127.0.0.1\\\\*\" and process.args : (\"2>&1\", \"1>\")\n", "risk_score": 47, "rule_id": "12f07955-1674-44f7-86b5-c35da0a6f41a", "severity": "medium", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json index aabeb4fb75ab59..79ecde48e5bda1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json @@ -3,14 +3,16 @@ "Elastic" ], "description": "Identifies a suspicious image load (wmiutils.dll) from Microsoft Office processes. This behavior may indicate adversarial activity where child processes are spawned via Windows Management Instrumentation (WMI). This technique can be used to execute code and evade traditional parent/child processes spawned from MS Office products.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious WMI Image Load from MS Office", - "query": "library where process.name in (\"WINWORD.EXE\", \"EXCEL.EXE\", \"POWERPNT.EXE\", \"MSPUB.EXE\", \"MSACCESS.EXE\") and\n event.action == \"load\" and\n event.category == \"library\" and\n file.name == \"wmiutils.dll\"\n", + "query": "library where process.name : (\"WINWORD.EXE\", \"EXCEL.EXE\", \"POWERPNT.EXE\", \"MSPUB.EXE\", \"MSACCESS.EXE\") and\n event.action : \"load\" and\n event.category : \"library\" and\n dll.name : \"wmiutils.dll\"\n", "references": [ "https://medium.com/threatpunter/detecting-adversary-tradecraft-with-image-load-event-logging-and-eql-8de93338c16" ], @@ -43,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json new file mode 100644 index 00000000000000..b32c66bbc1dbf0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies suspicious child processes of a Java Archive (JAR) file. JAR files may be used to deliver malware in order to evade detection.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious JAR Child Process", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"java\" and\n process.name : (\"sh\", \"bash\", \"dash\", \"ksh\", \"tcsh\", \"zsh\", \"curl\", \"wget\") and\n process.args : \"-jar\" and process.args : \"*.jar\" and\n /* Add any FP's here */\n not process.executable : (\"/Users/*/.sdkman/*\", \"/Library/Java/JavaVirtualMachines/*\") and\n not process.args : (\"/usr/local/*\", \"/Users/*/github.com/*\", \"/Users/*/src/*\")\n", + "risk_score": 47, + "rule_id": "8acb7614-1d92-4359-bfcf-478b6d9de150", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/", + "subtechnique": [ + { + "id": "T1059.007", + "name": "JavaScript/JScript", + "reference": "https://attack.mitre.org/techniques/T1059/007/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json index 01096ce781eb1f..5a6d440d7e081c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json index bd25919944e1f8..c21848f01080a0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious PowerShell Engine ImageLoad", - "query": "library where file.name : (\"System.Management.Automation.ni.dll\", \"System.Management.Automation.dll\") and\n/* add false positives relevant to your environment here */\nnot process.executable : (\"C:\\\\Windows\\\\System32\\\\RemoteFXvGPUDisablement.exe\", \"C:\\\\Windows\\\\System32\\\\sdiagnhost.exe\", \"C:\\\\Program Files*\\\\*.exe\") and\n not process.name : (\n \"Altaro.SubAgent.exe\",\n \"AppV_Manage.exe\",\n \"azureadconnect.exe\",\n \"CcmExec.exe\",\n \"configsyncrun.exe\",\n \"choco.exe\",\n \"ctxappvservice.exe\",\n \"DVLS.Console.exe\",\n \"edgetransport.exe\",\n \"exsetup.exe\",\n \"forefrontactivedirectoryconnector.exe\",\n \"InstallUtil.exe\",\n \"JenkinsOnDesktop.exe\",\n \"Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe\",\n \"mmc.exe\",\n \"mscorsvw.exe\",\n \"msexchangedelivery.exe\",\n \"msexchangefrontendtransport.exe\",\n \"msexchangehmworker.exe\",\n \"msexchangesubmission.exe\",\n \"msiexec.exe\",\n \"MsiExec.exe\",\n \"noderunner.exe\",\n \"NServiceBus.Host.exe\",\n \"NServiceBus.Host32.exe\",\n \"NServiceBus.Hosting.Azure.HostProcess.exe\",\n \"OuiGui.WPF.exe\",\n \"powershell.exe\",\n \"powershell_ise.exe\",\n \"pwsh.exe\",\n \"SCCMCliCtrWPF.exe\",\n \"ScriptEditor.exe\",\n \"ScriptRunner.exe\",\n \"sdiagnhost.exe\",\n \"servermanager.exe\",\n \"setup100.exe\",\n \"ServiceHub.VSDetouredHost.exe\",\n \"SPCAF.Client.exe\",\n \"SPCAF.SettingsEditor.exe\",\n \"SQLPS.exe\",\n \"telemetryservice.exe\",\n \"UMWorkerProcess.exe\",\n \"w3wp.exe\",\n \"wsmprovhost.exe\"\n )\n", + "query": "library where dll.name : (\"System.Management.Automation.ni.dll\", \"System.Management.Automation.dll\") and\n/* add false positives relevant to your environment here */\nnot process.executable : (\"C:\\\\Windows\\\\System32\\\\RemoteFXvGPUDisablement.exe\", \"C:\\\\Windows\\\\System32\\\\sdiagnhost.exe\", \"C:\\\\Program Files*\\\\*.exe\") and\n not process.name :\n (\n \"Altaro.SubAgent.exe\",\n \"AppV_Manage.exe\",\n \"azureadconnect.exe\",\n \"CcmExec.exe\",\n \"configsyncrun.exe\",\n \"choco.exe\",\n \"ctxappvservice.exe\",\n \"DVLS.Console.exe\",\n \"edgetransport.exe\",\n \"exsetup.exe\",\n \"forefrontactivedirectoryconnector.exe\",\n \"InstallUtil.exe\",\n \"JenkinsOnDesktop.exe\",\n \"Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe\",\n \"mmc.exe\",\n \"mscorsvw.exe\",\n \"msexchangedelivery.exe\",\n \"msexchangefrontendtransport.exe\",\n \"msexchangehmworker.exe\",\n \"msexchangesubmission.exe\",\n \"msiexec.exe\",\n \"MsiExec.exe\",\n \"noderunner.exe\",\n \"NServiceBus.Host.exe\",\n \"NServiceBus.Host32.exe\",\n \"NServiceBus.Hosting.Azure.HostProcess.exe\",\n \"OuiGui.WPF.exe\",\n \"powershell.exe\",\n \"powershell_ise.exe\",\n \"pwsh.exe\",\n \"SCCMCliCtrWPF.exe\",\n \"ScriptEditor.exe\",\n \"ScriptRunner.exe\",\n \"sdiagnhost.exe\",\n \"servermanager.exe\",\n \"setup100.exe\",\n \"ServiceHub.VSDetouredHost.exe\",\n \"SPCAF.Client.exe\",\n \"SPCAF.SettingsEditor.exe\",\n \"SQLPS.exe\",\n \"telemetryservice.exe\",\n \"UMWorkerProcess.exe\",\n \"w3wp.exe\",\n \"wsmprovhost.exe\"\n )\n", "risk_score": 47, "rule_id": "852c1f19-68e8-43a6-9dce-340771fe1be3", "severity": "medium", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_psexesvc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_psexesvc.json index 25ac9815ebbb89..1889310e10df8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_psexesvc.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_psexesvc.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_short_program_name.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_short_program_name.json index 58236ba2023415..00ca1613f1c9bd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_short_program_name.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_short_program_name.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -24,5 +25,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json index 28fedc8e7bc3a1..98ffa167166d4e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json @@ -6,9 +6,11 @@ "false_positives": [ "The HTML Help executable program (hh.exe) runs whenever a user clicks a compiled help (.chm) file or menu item that opens the help file inside the Help Viewer. This is not always malicious, but adversaries may abuse this technology to conceal malicious code." ], + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -59,5 +61,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json index 289513bdaf562b..edf29080bf08e8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json index 5209c30dc33e69..98b6d40d233726 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_net_com_assemblies.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -57,5 +58,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json index e64ee320373ada..5e7ac017e1c912 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json index 475c4f4937a54b..6d3a7bf9592fa8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json @@ -7,7 +7,8 @@ "index": [ "auditbeat-*", "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -55,5 +56,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_vssadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_vssadmin.json index 3f772a80e680b4..ec69bd9ceab25d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_vssadmin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_vssadmin.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts index c59b2942a8d636..87e5378aaf315f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -10,467 +10,548 @@ // - detection-rules repo using CLI command build-release // Do not hand edit. Run script/command to regenerate package information instead -import rule1 from './apm_403_response_to_a_post.json'; -import rule2 from './apm_405_response_method_not_allowed.json'; -import rule3 from './apm_null_user_agent.json'; -import rule4 from './apm_sqlmap_user_agent.json'; -import rule5 from './command_and_control_dns_directly_to_the_internet.json'; -import rule6 from './command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json'; -import rule7 from './command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json'; -import rule8 from './command_and_control_nat_traversal_port_activity.json'; -import rule9 from './command_and_control_port_26_activity.json'; -import rule10 from './command_and_control_port_8000_activity_to_the_internet.json'; -import rule11 from './command_and_control_pptp_point_to_point_tunneling_protocol_activity.json'; -import rule12 from './command_and_control_proxy_port_activity_to_the_internet.json'; -import rule13 from './command_and_control_rdp_remote_desktop_protocol_from_the_internet.json'; -import rule14 from './command_and_control_smtp_to_the_internet.json'; -import rule15 from './command_and_control_sql_server_port_activity_to_the_internet.json'; -import rule16 from './command_and_control_ssh_secure_shell_from_the_internet.json'; -import rule17 from './command_and_control_ssh_secure_shell_to_the_internet.json'; -import rule18 from './command_and_control_telnet_port_activity.json'; -import rule19 from './command_and_control_tor_activity_to_the_internet.json'; -import rule20 from './command_and_control_vnc_virtual_network_computing_from_the_internet.json'; -import rule21 from './command_and_control_vnc_virtual_network_computing_to_the_internet.json'; -import rule22 from './credential_access_tcpdump_activity.json'; -import rule23 from './defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json'; -import rule24 from './defense_evasion_clearing_windows_event_logs.json'; -import rule25 from './defense_evasion_delete_volume_usn_journal_with_fsutil.json'; -import rule26 from './defense_evasion_deleting_backup_catalogs_with_wbadmin.json'; -import rule27 from './defense_evasion_disable_windows_firewall_rules_with_netsh.json'; -import rule28 from './defense_evasion_encoding_or_decoding_files_via_certutil.json'; -import rule29 from './defense_evasion_execution_via_trusted_developer_utilities.json'; -import rule30 from './defense_evasion_misc_lolbin_connecting_to_the_internet.json'; -import rule31 from './defense_evasion_msbuild_making_network_connections.json'; -import rule32 from './defense_evasion_unusual_network_connection_via_rundll32.json'; -import rule33 from './defense_evasion_unusual_process_network_connection.json'; -import rule34 from './defense_evasion_via_filter_manager.json'; -import rule35 from './defense_evasion_volume_shadow_copy_deletion_via_wmic.json'; -import rule36 from './discovery_process_discovery_via_tasklist_command.json'; -import rule37 from './discovery_whoami_command_activity.json'; -import rule38 from './discovery_whoami_commmand.json'; -import rule39 from './endpoint_adversary_behavior_detected.json'; -import rule40 from './endpoint_cred_dumping_detected.json'; -import rule41 from './endpoint_cred_dumping_prevented.json'; -import rule42 from './endpoint_cred_manipulation_detected.json'; -import rule43 from './endpoint_cred_manipulation_prevented.json'; -import rule44 from './endpoint_exploit_detected.json'; -import rule45 from './endpoint_exploit_prevented.json'; -import rule46 from './endpoint_malware_detected.json'; -import rule47 from './endpoint_malware_prevented.json'; -import rule48 from './endpoint_permission_theft_detected.json'; -import rule49 from './endpoint_permission_theft_prevented.json'; -import rule50 from './endpoint_process_injection_detected.json'; -import rule51 from './endpoint_process_injection_prevented.json'; -import rule52 from './endpoint_ransomware_detected.json'; -import rule53 from './endpoint_ransomware_prevented.json'; -import rule54 from './execution_command_prompt_connecting_to_the_internet.json'; -import rule55 from './execution_command_shell_started_by_powershell.json'; -import rule56 from './execution_command_shell_started_by_svchost.json'; -import rule57 from './execution_html_help_executable_program_connecting_to_the_internet.json'; -import rule58 from './execution_psexec_lateral_movement_command.json'; -import rule59 from './execution_register_server_program_connecting_to_the_internet.json'; -import rule60 from './execution_via_compiled_html_file.json'; -import rule61 from './impact_volume_shadow_copy_deletion_via_vssadmin.json'; -import rule62 from './initial_access_rdp_remote_desktop_protocol_to_the_internet.json'; -import rule63 from './initial_access_rpc_remote_procedure_call_from_the_internet.json'; -import rule64 from './initial_access_rpc_remote_procedure_call_to_the_internet.json'; -import rule65 from './initial_access_script_executing_powershell.json'; -import rule66 from './initial_access_smb_windows_file_sharing_activity_to_the_internet.json'; -import rule67 from './initial_access_suspicious_ms_office_child_process.json'; -import rule68 from './initial_access_suspicious_ms_outlook_child_process.json'; -import rule69 from './lateral_movement_direct_outbound_smb_connection.json'; -import rule70 from './lateral_movement_local_service_commands.json'; -import rule71 from './linux_hping_activity.json'; -import rule72 from './linux_iodine_activity.json'; -import rule73 from './linux_mknod_activity.json'; -import rule74 from './linux_netcat_network_connection.json'; -import rule75 from './linux_nmap_activity.json'; -import rule76 from './linux_nping_activity.json'; -import rule77 from './linux_process_started_in_temp_directory.json'; -import rule78 from './linux_socat_activity.json'; -import rule79 from './linux_strace_activity.json'; -import rule80 from './persistence_adobe_hijack_persistence.json'; -import rule81 from './persistence_kernel_module_activity.json'; -import rule82 from './persistence_local_scheduled_task_commands.json'; -import rule83 from './persistence_priv_escalation_via_accessibility_features.json'; -import rule84 from './persistence_shell_activity_by_web_server.json'; -import rule85 from './persistence_system_shells_via_services.json'; -import rule86 from './persistence_user_account_creation.json'; -import rule87 from './persistence_via_application_shimming.json'; -import rule88 from './privilege_escalation_unusual_parentchild_relationship.json'; -import rule89 from './defense_evasion_modification_of_boot_config.json'; -import rule90 from './privilege_escalation_uac_bypass_event_viewer.json'; -import rule91 from './defense_evasion_msxsl_network.json'; -import rule92 from './discovery_net_command_system_account.json'; -import rule93 from './command_and_control_certutil_network_connection.json'; -import rule94 from './defense_evasion_cve_2020_0601.json'; -import rule95 from './credential_access_credential_dumping_msbuild.json'; -import rule96 from './defense_evasion_execution_msbuild_started_by_office_app.json'; -import rule97 from './defense_evasion_execution_msbuild_started_by_script.json'; -import rule98 from './defense_evasion_execution_msbuild_started_by_system_process.json'; -import rule99 from './defense_evasion_execution_msbuild_started_renamed.json'; -import rule100 from './defense_evasion_execution_msbuild_started_unusal_process.json'; -import rule101 from './defense_evasion_injection_msbuild.json'; -import rule102 from './execution_via_net_com_assemblies.json'; -import rule103 from './ml_linux_anomalous_network_activity.json'; -import rule104 from './ml_linux_anomalous_network_port_activity.json'; -import rule105 from './ml_linux_anomalous_network_service.json'; -import rule106 from './ml_linux_anomalous_network_url_activity.json'; -import rule107 from './ml_linux_anomalous_process_all_hosts.json'; -import rule108 from './ml_linux_anomalous_user_name.json'; -import rule109 from './ml_packetbeat_dns_tunneling.json'; -import rule110 from './ml_packetbeat_rare_dns_question.json'; -import rule111 from './ml_packetbeat_rare_server_domain.json'; -import rule112 from './ml_packetbeat_rare_urls.json'; -import rule113 from './ml_packetbeat_rare_user_agent.json'; -import rule114 from './ml_rare_process_by_host_linux.json'; -import rule115 from './ml_rare_process_by_host_windows.json'; -import rule116 from './ml_suspicious_login_activity.json'; -import rule117 from './ml_windows_anomalous_network_activity.json'; -import rule118 from './ml_windows_anomalous_path_activity.json'; -import rule119 from './ml_windows_anomalous_process_all_hosts.json'; -import rule120 from './ml_windows_anomalous_process_creation.json'; -import rule121 from './ml_windows_anomalous_script.json'; -import rule122 from './ml_windows_anomalous_service.json'; -import rule123 from './ml_windows_anomalous_user_name.json'; -import rule124 from './ml_windows_rare_user_runas_event.json'; -import rule125 from './ml_windows_rare_user_type10_remote_login.json'; -import rule126 from './execution_suspicious_pdf_reader.json'; -import rule127 from './privilege_escalation_sudoers_file_mod.json'; -import rule128 from './defense_evasion_iis_httplogging_disabled.json'; -import rule129 from './execution_python_tty_shell.json'; -import rule130 from './execution_perl_tty_shell.json'; -import rule131 from './defense_evasion_base16_or_base32_encoding_or_decoding_activity.json'; -import rule132 from './defense_evasion_base64_encoding_or_decoding_activity.json'; -import rule133 from './defense_evasion_hex_encoding_or_decoding_activity.json'; -import rule134 from './defense_evasion_file_mod_writable_dir.json'; -import rule135 from './defense_evasion_disable_selinux_attempt.json'; -import rule136 from './discovery_kernel_module_enumeration.json'; -import rule137 from './lateral_movement_telnet_network_activity_external.json'; -import rule138 from './lateral_movement_telnet_network_activity_internal.json'; -import rule139 from './privilege_escalation_setgid_bit_set_via_chmod.json'; -import rule140 from './privilege_escalation_setuid_bit_set_via_chmod.json'; -import rule141 from './defense_evasion_attempt_to_disable_iptables_or_firewall.json'; -import rule142 from './defense_evasion_kernel_module_removal.json'; -import rule143 from './defense_evasion_attempt_to_disable_syslog_service.json'; -import rule144 from './defense_evasion_file_deletion_via_shred.json'; -import rule145 from './discovery_virtual_machine_fingerprinting.json'; -import rule146 from './defense_evasion_hidden_file_dir_tmp.json'; -import rule147 from './defense_evasion_deletion_of_bash_command_line_history.json'; -import rule148 from './impact_cloudwatch_log_group_deletion.json'; -import rule149 from './impact_cloudwatch_log_stream_deletion.json'; -import rule150 from './impact_rds_instance_cluster_stoppage.json'; -import rule151 from './persistence_attempt_to_deactivate_mfa_for_okta_user_account.json'; -import rule152 from './persistence_rds_cluster_creation.json'; -import rule153 from './credential_access_attempted_bypass_of_okta_mfa.json'; -import rule154 from './defense_evasion_waf_acl_deletion.json'; -import rule155 from './impact_attempt_to_revoke_okta_api_token.json'; -import rule156 from './impact_iam_group_deletion.json'; -import rule157 from './impact_possible_okta_dos_attack.json'; -import rule158 from './impact_rds_cluster_deletion.json'; -import rule159 from './initial_access_suspicious_activity_reported_by_okta_user.json'; -import rule160 from './okta_attempt_to_deactivate_okta_policy.json'; -import rule161 from './okta_attempt_to_deactivate_okta_policy_rule.json'; -import rule162 from './okta_attempt_to_modify_okta_network_zone.json'; -import rule163 from './okta_attempt_to_modify_okta_policy.json'; -import rule164 from './okta_attempt_to_modify_okta_policy_rule.json'; -import rule165 from './okta_threat_detected_by_okta_threatinsight.json'; -import rule166 from './persistence_administrator_privileges_assigned_to_okta_group.json'; -import rule167 from './persistence_attempt_to_create_okta_api_token.json'; -import rule168 from './persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json'; -import rule169 from './defense_evasion_cloudtrail_logging_deleted.json'; -import rule170 from './defense_evasion_ec2_network_acl_deletion.json'; -import rule171 from './impact_iam_deactivate_mfa_device.json'; -import rule172 from './defense_evasion_s3_bucket_configuration_deletion.json'; -import rule173 from './defense_evasion_guardduty_detector_deletion.json'; -import rule174 from './okta_attempt_to_delete_okta_policy.json'; -import rule175 from './credential_access_iam_user_addition_to_group.json'; -import rule176 from './persistence_ec2_network_acl_creation.json'; -import rule177 from './impact_ec2_disable_ebs_encryption.json'; -import rule178 from './persistence_iam_group_creation.json'; -import rule179 from './defense_evasion_waf_rule_or_rule_group_deletion.json'; -import rule180 from './collection_cloudtrail_logging_created.json'; -import rule181 from './defense_evasion_cloudtrail_logging_suspended.json'; -import rule182 from './impact_cloudtrail_logging_updated.json'; -import rule183 from './initial_access_console_login_root.json'; -import rule184 from './defense_evasion_cloudwatch_alarm_deletion.json'; -import rule185 from './defense_evasion_ec2_flow_log_deletion.json'; -import rule186 from './defense_evasion_configuration_recorder_stopped.json'; -import rule187 from './exfiltration_ec2_snapshot_change_activity.json'; -import rule188 from './defense_evasion_config_service_rule_deletion.json'; -import rule189 from './okta_attempt_to_modify_or_delete_application_sign_on_policy.json'; -import rule190 from './command_and_control_download_rar_powershell_from_internet.json'; -import rule191 from './initial_access_password_recovery.json'; -import rule192 from './command_and_control_cobalt_strike_beacon.json'; -import rule193 from './command_and_control_fin7_c2_behavior.json'; -import rule194 from './command_and_control_halfbaked_beacon.json'; -import rule195 from './credential_access_secretsmanager_getsecretvalue.json'; -import rule196 from './initial_access_via_system_manager.json'; -import rule197 from './privilege_escalation_root_login_without_mfa.json'; -import rule198 from './privilege_escalation_updateassumerolepolicy.json'; -import rule199 from './impact_hosts_file_modified.json'; -import rule200 from './elastic_endpoint.json'; -import rule201 from './external_alerts.json'; -import rule202 from './ml_cloudtrail_error_message_spike.json'; -import rule203 from './ml_cloudtrail_rare_error_code.json'; -import rule204 from './ml_cloudtrail_rare_method_by_city.json'; -import rule205 from './ml_cloudtrail_rare_method_by_country.json'; -import rule206 from './ml_cloudtrail_rare_method_by_user.json'; -import rule207 from './credential_access_aws_iam_assume_role_brute_force.json'; -import rule208 from './credential_access_okta_brute_force_or_password_spraying.json'; -import rule209 from './initial_access_unusual_dns_service_children.json'; -import rule210 from './initial_access_unusual_dns_service_file_writes.json'; -import rule211 from './lateral_movement_dns_server_overflow.json'; -import rule212 from './credential_access_root_console_failure_brute_force.json'; -import rule213 from './initial_access_unsecure_elasticsearch_node.json'; -import rule214 from './credential_access_domain_backup_dpapi_private_keys.json'; -import rule215 from './persistence_gpo_schtask_service_creation.json'; -import rule216 from './credential_access_compress_credentials_keychains.json'; -import rule217 from './credential_access_kerberosdump_kcc.json'; -import rule218 from './defense_evasion_attempt_del_quarantine_attrib.json'; -import rule219 from './execution_suspicious_psexesvc.json'; -import rule220 from './execution_via_xp_cmdshell_mssql_stored_procedure.json'; -import rule221 from './privilege_escalation_printspooler_service_suspicious_file.json'; -import rule222 from './privilege_escalation_printspooler_suspicious_spl_file.json'; -import rule223 from './defense_evasion_azure_diagnostic_settings_deletion.json'; -import rule224 from './execution_command_virtual_machine.json'; -import rule225 from './execution_via_hidden_shell_conhost.json'; -import rule226 from './impact_resource_group_deletion.json'; -import rule227 from './persistence_via_telemetrycontroller_scheduledtask_hijack.json'; -import rule228 from './persistence_via_update_orchestrator_service_hijack.json'; -import rule229 from './collection_update_event_hub_auth_rule.json'; -import rule230 from './credential_access_iis_apppoolsa_pwd_appcmd.json'; -import rule231 from './credential_access_iis_connectionstrings_dumping.json'; -import rule232 from './defense_evasion_event_hub_deletion.json'; -import rule233 from './defense_evasion_firewall_policy_deletion.json'; -import rule234 from './defense_evasion_sdelete_like_filename_rename.json'; -import rule235 from './lateral_movement_remote_ssh_login_enabled.json'; -import rule236 from './persistence_azure_automation_account_created.json'; -import rule237 from './persistence_azure_automation_runbook_created_or_modified.json'; -import rule238 from './persistence_azure_automation_webhook_created.json'; -import rule239 from './privilege_escalation_uac_bypass_diskcleanup_hijack.json'; -import rule240 from './credential_access_attempts_to_brute_force_okta_user_account.json'; -import rule241 from './credential_access_storage_account_key_regenerated.json'; -import rule242 from './defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json'; -import rule243 from './defense_evasion_system_critical_proc_abnormal_file_activity.json'; -import rule244 from './defense_evasion_unusual_system_vp_child_program.json'; -import rule245 from './discovery_blob_container_access_mod.json'; -import rule246 from './persistence_mfa_disabled_for_azure_user.json'; -import rule247 from './persistence_user_added_as_owner_for_azure_application.json'; -import rule248 from './persistence_user_added_as_owner_for_azure_service_principal.json'; -import rule249 from './defense_evasion_dotnet_compiler_parent_process.json'; -import rule250 from './defense_evasion_suspicious_managedcode_host_process.json'; -import rule251 from './execution_command_shell_started_by_unusual_process.json'; -import rule252 from './defense_evasion_masquerading_as_elastic_endpoint_process.json'; -import rule253 from './defense_evasion_masquerading_suspicious_werfault_childproc.json'; -import rule254 from './defense_evasion_masquerading_werfault.json'; -import rule255 from './credential_access_key_vault_modified.json'; -import rule256 from './credential_access_mimikatz_memssp_default_logs.json'; -import rule257 from './defense_evasion_code_injection_conhost.json'; -import rule258 from './defense_evasion_network_watcher_deletion.json'; -import rule259 from './initial_access_external_guest_user_invite.json'; -import rule260 from './defense_evasion_masquerading_renamed_autoit.json'; -import rule261 from './impact_azure_automation_runbook_deleted.json'; -import rule262 from './initial_access_consent_grant_attack_via_azure_registered_application.json'; -import rule263 from './persistence_azure_conditional_access_policy_modified.json'; -import rule264 from './persistence_azure_privileged_identity_management_role_modified.json'; -import rule265 from './command_and_control_teamviewer_remote_file_copy.json'; -import rule266 from './defense_evasion_installutil_beacon.json'; -import rule267 from './defense_evasion_mshta_beacon.json'; -import rule268 from './defense_evasion_network_connection_from_windows_binary.json'; -import rule269 from './defense_evasion_rundll32_no_arguments.json'; -import rule270 from './defense_evasion_suspicious_scrobj_load.json'; -import rule271 from './defense_evasion_suspicious_wmi_script.json'; -import rule272 from './execution_ms_office_written_file.json'; -import rule273 from './execution_pdf_written_file.json'; -import rule274 from './lateral_movement_cmd_service.json'; -import rule275 from './persistence_app_compat_shim.json'; -import rule276 from './command_and_control_remote_file_copy_desktopimgdownldr.json'; -import rule277 from './command_and_control_remote_file_copy_mpcmdrun.json'; -import rule278 from './defense_evasion_execution_suspicious_explorer_winword.json'; -import rule279 from './defense_evasion_suspicious_zoom_child_process.json'; -import rule280 from './ml_linux_anomalous_compiler_activity.json'; -import rule281 from './ml_linux_anomalous_kernel_module_arguments.json'; -import rule282 from './ml_linux_anomalous_sudo_activity.json'; -import rule283 from './ml_linux_system_information_discovery.json'; -import rule284 from './ml_linux_system_network_configuration_discovery.json'; -import rule285 from './ml_linux_system_network_connection_discovery.json'; -import rule286 from './ml_linux_system_process_discovery.json'; -import rule287 from './ml_linux_system_user_discovery.json'; -import rule288 from './discovery_post_exploitation_public_ip_reconnaissance.json'; -import rule289 from './initial_access_zoom_meeting_with_no_passcode.json'; -import rule290 from './defense_evasion_gcp_logging_sink_deletion.json'; -import rule291 from './defense_evasion_gcp_pub_sub_topic_deletion.json'; -import rule292 from './defense_evasion_gcp_firewall_rule_created.json'; -import rule293 from './defense_evasion_gcp_firewall_rule_deleted.json'; -import rule294 from './defense_evasion_gcp_firewall_rule_modified.json'; -import rule295 from './defense_evasion_gcp_logging_bucket_deletion.json'; -import rule296 from './defense_evasion_gcp_storage_bucket_permissions_modified.json'; -import rule297 from './impact_gcp_storage_bucket_deleted.json'; -import rule298 from './initial_access_gcp_iam_custom_role_creation.json'; -import rule299 from './persistence_gcp_iam_service_account_key_deletion.json'; -import rule300 from './persistence_gcp_key_created_for_service_account.json'; -import rule301 from './defense_evasion_gcp_storage_bucket_configuration_modified.json'; -import rule302 from './exfiltration_gcp_logging_sink_modification.json'; -import rule303 from './impact_gcp_iam_role_deletion.json'; -import rule304 from './impact_gcp_service_account_deleted.json'; -import rule305 from './impact_gcp_service_account_disabled.json'; -import rule306 from './impact_gcp_virtual_private_cloud_network_deleted.json'; -import rule307 from './impact_gcp_virtual_private_cloud_route_created.json'; -import rule308 from './impact_gcp_virtual_private_cloud_route_deleted.json'; -import rule309 from './ml_linux_anomalous_metadata_process.json'; -import rule310 from './ml_linux_anomalous_metadata_user.json'; -import rule311 from './ml_windows_anomalous_metadata_process.json'; -import rule312 from './ml_windows_anomalous_metadata_user.json'; -import rule313 from './persistence_gcp_service_account_created.json'; -import rule314 from './collection_gcp_pub_sub_subscription_creation.json'; -import rule315 from './collection_gcp_pub_sub_topic_creation.json'; -import rule316 from './defense_evasion_gcp_pub_sub_subscription_deletion.json'; -import rule317 from './persistence_azure_pim_user_added_global_admin.json'; -import rule318 from './command_and_control_cobalt_strike_default_teamserver_cert.json'; -import rule319 from './defense_evasion_enable_inbound_rdp_with_netsh.json'; -import rule320 from './defense_evasion_execution_lolbas_wuauclt.json'; -import rule321 from './privilege_escalation_unusual_svchost_childproc_childless.json'; -import rule322 from './lateral_movement_rdp_tunnel_plink.json'; -import rule323 from './privilege_escalation_uac_bypass_winfw_mmc_hijack.json'; -import rule324 from './persistence_ms_office_addins_file.json'; -import rule325 from './discovery_adfind_command_activity.json'; -import rule326 from './discovery_security_software_wmic.json'; -import rule327 from './execution_command_shell_via_rundll32.json'; -import rule328 from './execution_suspicious_cmd_wmi.json'; -import rule329 from './lateral_movement_via_startup_folder_rdp_smb.json'; -import rule330 from './privilege_escalation_uac_bypass_com_interface_icmluautil.json'; -import rule331 from './privilege_escalation_uac_bypass_mock_windir.json'; -import rule332 from './defense_evasion_potential_processherpaderping.json'; -import rule333 from './privilege_escalation_uac_bypass_dll_sideloading.json'; -import rule334 from './execution_shared_modules_local_sxs_dll.json'; -import rule335 from './privilege_escalation_uac_bypass_com_clipup.json'; -import rule336 from './initial_access_via_explorer_suspicious_child_parent_args.json'; -import rule337 from './execution_from_unusual_directory.json'; -import rule338 from './execution_from_unusual_path_cmdline.json'; -import rule339 from './credential_access_kerberoasting_unusual_process.json'; -import rule340 from './discovery_peripheral_device.json'; -import rule341 from './lateral_movement_mount_hidden_or_webdav_share_net.json'; -import rule342 from './defense_evasion_deleting_websvr_access_logs.json'; -import rule343 from './defense_evasion_log_files_deleted.json'; -import rule344 from './defense_evasion_timestomp_touch.json'; -import rule345 from './lateral_movement_dcom_hta.json'; -import rule346 from './lateral_movement_execution_via_file_shares_sequence.json'; -import rule347 from './privilege_escalation_uac_bypass_com_ieinstal.json'; -import rule348 from './command_and_control_common_webservices.json'; -import rule349 from './command_and_control_encrypted_channel_freesslcert.json'; -import rule350 from './defense_evasion_process_termination_followed_by_deletion.json'; -import rule351 from './lateral_movement_remote_file_copy_hidden_share.json'; -import rule352 from './attempt_to_deactivate_okta_network_zone.json'; -import rule353 from './attempt_to_delete_okta_network_zone.json'; -import rule354 from './lateral_movement_dcom_mmc20.json'; -import rule355 from './lateral_movement_dcom_shellwindow_shellbrowserwindow.json'; -import rule356 from './okta_attempt_to_deactivate_okta_application.json'; -import rule357 from './okta_attempt_to_delete_okta_application.json'; -import rule358 from './okta_attempt_to_delete_okta_policy_rule.json'; -import rule359 from './okta_attempt_to_modify_okta_application.json'; -import rule360 from './persistence_administrator_role_assigned_to_okta_user.json'; -import rule361 from './lateral_movement_executable_tool_transfer_smb.json'; -import rule362 from './command_and_control_dns_tunneling_nslookup.json'; -import rule363 from './lateral_movement_execution_from_tsclient_mup.json'; -import rule364 from './lateral_movement_rdp_sharprdp_target.json'; -import rule365 from './persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json'; -import rule366 from './execution_suspicious_short_program_name.json'; -import rule367 from './lateral_movement_incoming_wmi.json'; -import rule368 from './persistence_via_hidden_run_key_valuename.json'; -import rule369 from './credential_access_potential_ssh_bruteforce.json'; -import rule370 from './credential_access_promt_for_pwd_via_osascript.json'; -import rule371 from './lateral_movement_remote_services.json'; -import rule372 from './application_added_to_google_workspace_domain.json'; -import rule373 from './domain_added_to_google_workspace_trusted_domains.json'; -import rule374 from './execution_suspicious_image_load_wmi_ms_office.json'; -import rule375 from './execution_suspicious_powershell_imgload.json'; -import rule376 from './google_workspace_admin_role_deletion.json'; -import rule377 from './google_workspace_mfa_enforcement_disabled.json'; -import rule378 from './google_workspace_policy_modified.json'; -import rule379 from './mfa_disabled_for_google_workspace_organization.json'; -import rule380 from './persistence_evasion_registry_ifeo_injection.json'; -import rule381 from './persistence_google_workspace_admin_role_assigned_to_user.json'; -import rule382 from './persistence_google_workspace_custom_admin_role_created.json'; -import rule383 from './persistence_google_workspace_role_modified.json'; -import rule384 from './persistence_suspicious_image_load_scheduled_task_ms_office.json'; -import rule385 from './defense_evasion_masquerading_trusted_directory.json'; -import rule386 from './exfiltration_microsoft_365_exchange_transport_rule_creation.json'; -import rule387 from './initial_access_microsoft_365_exchange_safelinks_disabled.json'; -import rule388 from './microsoft_365_exchange_dkim_signing_config_disabled.json'; -import rule389 from './persistence_appcertdlls_registry.json'; -import rule390 from './persistence_appinitdlls_registry.json'; -import rule391 from './persistence_registry_uncommon.json'; -import rule392 from './persistence_run_key_and_startup_broad.json'; -import rule393 from './persistence_services_registry.json'; -import rule394 from './persistence_startup_folder_file_written_by_suspicious_process.json'; -import rule395 from './persistence_startup_folder_scripts.json'; -import rule396 from './persistence_suspicious_com_hijack_registry.json'; -import rule397 from './persistence_via_lsa_security_support_provider_registry.json'; -import rule398 from './defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json'; -import rule399 from './defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json'; -import rule400 from './defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json'; -import rule401 from './exfiltration_microsoft_365_exchange_transport_rule_mod.json'; -import rule402 from './initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json'; -import rule403 from './initial_access_microsoft_365_exchange_anti_phish_rule_mod.json'; -import rule404 from './lateral_movement_suspicious_rdp_client_imageload.json'; -import rule405 from './persistence_runtime_run_key_startup_susp_procs.json'; -import rule406 from './persistence_suspicious_scheduled_task_runtime.json'; -import rule407 from './defense_evasion_microsoft_365_exchange_dlp_policy_removed.json'; -import rule408 from './lateral_movement_scheduled_task_target.json'; -import rule409 from './persistence_microsoft_365_exchange_management_role_assignment.json'; -import rule410 from './persistence_microsoft_365_teams_guest_access_enabled.json'; -import rule411 from './credential_access_dump_registry_hives.json'; -import rule412 from './defense_evasion_scheduledjobs_at_protocol_enabled.json'; -import rule413 from './persistence_ms_outlook_vba_template.json'; -import rule414 from './persistence_suspicious_service_created_registry.json'; -import rule415 from './privilege_escalation_named_pipe_impersonation.json'; -import rule416 from './credential_access_cmdline_dump_tool.json'; -import rule417 from './credential_access_copy_ntds_sam_volshadowcp_cmdline.json'; -import rule418 from './credential_access_lsass_memdump_file_created.json'; -import rule419 from './lateral_movement_incoming_winrm_shell_execution.json'; -import rule420 from './lateral_movement_powershell_remoting_target.json'; -import rule421 from './defense_evasion_hide_encoded_executable_registry.json'; -import rule422 from './defense_evasion_port_forwarding_added_registry.json'; -import rule423 from './lateral_movement_rdp_enabled_registry.json'; -import rule424 from './privilege_escalation_printspooler_registry_copyfiles.json'; -import rule425 from './privilege_escalation_rogue_windir_environment_var.json'; -import rule426 from './initial_access_scripts_process_started_via_wmi.json'; -import rule427 from './command_and_control_iexplore_via_com.json'; -import rule428 from './command_and_control_remote_file_copy_scripts.json'; -import rule429 from './persistence_local_scheduled_task_scripting.json'; -import rule430 from './persistence_startup_folder_file_written_by_unsigned_process.json'; -import rule431 from './command_and_control_remote_file_copy_powershell.json'; -import rule432 from './credential_access_microsoft_365_brute_force_user_account_attempt.json'; -import rule433 from './microsoft_365_teams_custom_app_interaction_allowed.json'; -import rule434 from './persistence_microsoft_365_teams_external_access_enabled.json'; -import rule435 from './credential_access_microsoft_365_potential_password_spraying_attack.json'; -import rule436 from './defense_evasion_stop_process_service_threshold.json'; -import rule437 from './collection_winrar_encryption.json'; -import rule438 from './defense_evasion_unusual_dir_ads.json'; -import rule439 from './discovery_admin_recon.json'; -import rule440 from './discovery_file_dir_discovery.json'; -import rule441 from './discovery_net_view.json'; -import rule442 from './discovery_query_registry_via_reg.json'; -import rule443 from './discovery_remote_system_discovery_commands_windows.json'; -import rule444 from './persistence_via_windows_management_instrumentation_event_subscription.json'; -import rule445 from './execution_scripting_osascript_exec_followed_by_netcon.json'; -import rule446 from './execution_shell_execution_via_apple_scripting.json'; -import rule447 from './persistence_creation_change_launch_agents_file.json'; -import rule448 from './persistence_creation_modif_launch_deamon_sequence.json'; -import rule449 from './persistence_folder_action_scripts_runtime.json'; -import rule450 from './persistence_login_logout_hooks_defaults.json'; -import rule451 from './privilege_escalation_explicit_creds_via_apple_scripting.json'; -import rule452 from './command_and_control_sunburst_c2_activity_detected.json'; -import rule453 from './defense_evasion_azure_application_credential_modification.json'; -import rule454 from './defense_evasion_azure_service_principal_addition.json'; -import rule455 from './defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json'; -import rule456 from './execution_apt_solarwinds_backdoor_child_cmd_powershell.json'; -import rule457 from './execution_apt_solarwinds_backdoor_unusual_child_processes.json'; -import rule458 from './initial_access_azure_active_directory_powershell_signin.json'; -import rule459 from './collection_email_powershell_exchange_mailbox.json'; -import rule460 from './collection_persistence_powershell_exch_mailbox_activesync_add_device.json'; -import rule461 from './execution_scheduled_task_powershell_source.json'; +import rule1 from './credential_access_access_to_browser_credentials_procargs.json'; +import rule2 from './defense_evasion_tcc_bypass_mounted_apfs_access.json'; +import rule3 from './persistence_enable_root_account.json'; +import rule4 from './defense_evasion_unload_endpointsecurity_kext.json'; +import rule5 from './persistence_account_creation_hide_at_logon.json'; +import rule6 from './persistence_creation_hidden_login_item_osascript.json'; +import rule7 from './persistence_evasion_hidden_launch_agent_deamon_creation.json'; +import rule8 from './privilege_escalation_local_user_added_to_admin.json'; +import rule9 from './credential_access_keychain_pwd_retrieval_security_cmd.json'; +import rule10 from './credential_access_systemkey_dumping.json'; +import rule11 from './execution_defense_evasion_electron_app_childproc_node_js.json'; +import rule12 from './execution_revershell_via_shell_cmd.json'; +import rule13 from './persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json'; +import rule14 from './privilege_escalation_persistence_phantom_dll.json'; +import rule15 from './defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json'; +import rule16 from './lateral_movement_credential_access_kerberos_bifrostconsole.json'; +import rule17 from './lateral_movement_vpn_connection_attempt.json'; +import rule18 from './apm_403_response_to_a_post.json'; +import rule19 from './apm_405_response_method_not_allowed.json'; +import rule20 from './apm_null_user_agent.json'; +import rule21 from './apm_sqlmap_user_agent.json'; +import rule22 from './command_and_control_dns_directly_to_the_internet.json'; +import rule23 from './command_and_control_ftp_file_transfer_protocol_activity_to_the_internet.json'; +import rule24 from './command_and_control_irc_internet_relay_chat_protocol_activity_to_the_internet.json'; +import rule25 from './command_and_control_nat_traversal_port_activity.json'; +import rule26 from './command_and_control_port_26_activity.json'; +import rule27 from './command_and_control_port_8000_activity_to_the_internet.json'; +import rule28 from './command_and_control_pptp_point_to_point_tunneling_protocol_activity.json'; +import rule29 from './command_and_control_proxy_port_activity_to_the_internet.json'; +import rule30 from './command_and_control_rdp_remote_desktop_protocol_from_the_internet.json'; +import rule31 from './command_and_control_smtp_to_the_internet.json'; +import rule32 from './command_and_control_sql_server_port_activity_to_the_internet.json'; +import rule33 from './command_and_control_ssh_secure_shell_from_the_internet.json'; +import rule34 from './command_and_control_ssh_secure_shell_to_the_internet.json'; +import rule35 from './command_and_control_telnet_port_activity.json'; +import rule36 from './command_and_control_tor_activity_to_the_internet.json'; +import rule37 from './command_and_control_vnc_virtual_network_computing_from_the_internet.json'; +import rule38 from './command_and_control_vnc_virtual_network_computing_to_the_internet.json'; +import rule39 from './credential_access_tcpdump_activity.json'; +import rule40 from './defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json'; +import rule41 from './defense_evasion_clearing_windows_event_logs.json'; +import rule42 from './defense_evasion_delete_volume_usn_journal_with_fsutil.json'; +import rule43 from './defense_evasion_deleting_backup_catalogs_with_wbadmin.json'; +import rule44 from './defense_evasion_disable_windows_firewall_rules_with_netsh.json'; +import rule45 from './defense_evasion_encoding_or_decoding_files_via_certutil.json'; +import rule46 from './defense_evasion_execution_via_trusted_developer_utilities.json'; +import rule47 from './defense_evasion_misc_lolbin_connecting_to_the_internet.json'; +import rule48 from './defense_evasion_msbuild_making_network_connections.json'; +import rule49 from './defense_evasion_unusual_network_connection_via_rundll32.json'; +import rule50 from './defense_evasion_unusual_process_network_connection.json'; +import rule51 from './defense_evasion_via_filter_manager.json'; +import rule52 from './defense_evasion_volume_shadow_copy_deletion_via_wmic.json'; +import rule53 from './discovery_process_discovery_via_tasklist_command.json'; +import rule54 from './discovery_whoami_command_activity.json'; +import rule55 from './discovery_whoami_commmand.json'; +import rule56 from './endpoint_adversary_behavior_detected.json'; +import rule57 from './endpoint_cred_dumping_detected.json'; +import rule58 from './endpoint_cred_dumping_prevented.json'; +import rule59 from './endpoint_cred_manipulation_detected.json'; +import rule60 from './endpoint_cred_manipulation_prevented.json'; +import rule61 from './endpoint_exploit_detected.json'; +import rule62 from './endpoint_exploit_prevented.json'; +import rule63 from './endpoint_malware_detected.json'; +import rule64 from './endpoint_malware_prevented.json'; +import rule65 from './endpoint_permission_theft_detected.json'; +import rule66 from './endpoint_permission_theft_prevented.json'; +import rule67 from './endpoint_process_injection_detected.json'; +import rule68 from './endpoint_process_injection_prevented.json'; +import rule69 from './endpoint_ransomware_detected.json'; +import rule70 from './endpoint_ransomware_prevented.json'; +import rule71 from './execution_command_prompt_connecting_to_the_internet.json'; +import rule72 from './execution_command_shell_started_by_powershell.json'; +import rule73 from './execution_command_shell_started_by_svchost.json'; +import rule74 from './execution_html_help_executable_program_connecting_to_the_internet.json'; +import rule75 from './execution_psexec_lateral_movement_command.json'; +import rule76 from './execution_register_server_program_connecting_to_the_internet.json'; +import rule77 from './execution_via_compiled_html_file.json'; +import rule78 from './impact_volume_shadow_copy_deletion_via_vssadmin.json'; +import rule79 from './initial_access_rdp_remote_desktop_protocol_to_the_internet.json'; +import rule80 from './initial_access_rpc_remote_procedure_call_from_the_internet.json'; +import rule81 from './initial_access_rpc_remote_procedure_call_to_the_internet.json'; +import rule82 from './initial_access_script_executing_powershell.json'; +import rule83 from './initial_access_smb_windows_file_sharing_activity_to_the_internet.json'; +import rule84 from './initial_access_suspicious_ms_office_child_process.json'; +import rule85 from './initial_access_suspicious_ms_outlook_child_process.json'; +import rule86 from './lateral_movement_direct_outbound_smb_connection.json'; +import rule87 from './lateral_movement_local_service_commands.json'; +import rule88 from './linux_hping_activity.json'; +import rule89 from './linux_iodine_activity.json'; +import rule90 from './linux_mknod_activity.json'; +import rule91 from './linux_netcat_network_connection.json'; +import rule92 from './linux_nmap_activity.json'; +import rule93 from './linux_nping_activity.json'; +import rule94 from './linux_process_started_in_temp_directory.json'; +import rule95 from './linux_socat_activity.json'; +import rule96 from './linux_strace_activity.json'; +import rule97 from './persistence_adobe_hijack_persistence.json'; +import rule98 from './persistence_kernel_module_activity.json'; +import rule99 from './persistence_local_scheduled_task_commands.json'; +import rule100 from './persistence_priv_escalation_via_accessibility_features.json'; +import rule101 from './persistence_shell_activity_by_web_server.json'; +import rule102 from './persistence_system_shells_via_services.json'; +import rule103 from './persistence_user_account_creation.json'; +import rule104 from './persistence_via_application_shimming.json'; +import rule105 from './privilege_escalation_unusual_parentchild_relationship.json'; +import rule106 from './defense_evasion_modification_of_boot_config.json'; +import rule107 from './privilege_escalation_uac_bypass_event_viewer.json'; +import rule108 from './defense_evasion_msxsl_network.json'; +import rule109 from './discovery_net_command_system_account.json'; +import rule110 from './command_and_control_certutil_network_connection.json'; +import rule111 from './defense_evasion_cve_2020_0601.json'; +import rule112 from './credential_access_credential_dumping_msbuild.json'; +import rule113 from './defense_evasion_execution_msbuild_started_by_office_app.json'; +import rule114 from './defense_evasion_execution_msbuild_started_by_script.json'; +import rule115 from './defense_evasion_execution_msbuild_started_by_system_process.json'; +import rule116 from './defense_evasion_execution_msbuild_started_renamed.json'; +import rule117 from './defense_evasion_execution_msbuild_started_unusal_process.json'; +import rule118 from './defense_evasion_injection_msbuild.json'; +import rule119 from './execution_via_net_com_assemblies.json'; +import rule120 from './ml_linux_anomalous_network_activity.json'; +import rule121 from './ml_linux_anomalous_network_port_activity.json'; +import rule122 from './ml_linux_anomalous_network_service.json'; +import rule123 from './ml_linux_anomalous_network_url_activity.json'; +import rule124 from './ml_linux_anomalous_process_all_hosts.json'; +import rule125 from './ml_linux_anomalous_user_name.json'; +import rule126 from './ml_packetbeat_dns_tunneling.json'; +import rule127 from './ml_packetbeat_rare_dns_question.json'; +import rule128 from './ml_packetbeat_rare_server_domain.json'; +import rule129 from './ml_packetbeat_rare_urls.json'; +import rule130 from './ml_packetbeat_rare_user_agent.json'; +import rule131 from './ml_rare_process_by_host_linux.json'; +import rule132 from './ml_rare_process_by_host_windows.json'; +import rule133 from './ml_suspicious_login_activity.json'; +import rule134 from './ml_windows_anomalous_network_activity.json'; +import rule135 from './ml_windows_anomalous_path_activity.json'; +import rule136 from './ml_windows_anomalous_process_all_hosts.json'; +import rule137 from './ml_windows_anomalous_process_creation.json'; +import rule138 from './ml_windows_anomalous_script.json'; +import rule139 from './ml_windows_anomalous_service.json'; +import rule140 from './ml_windows_anomalous_user_name.json'; +import rule141 from './ml_windows_rare_user_runas_event.json'; +import rule142 from './ml_windows_rare_user_type10_remote_login.json'; +import rule143 from './execution_suspicious_pdf_reader.json'; +import rule144 from './privilege_escalation_sudoers_file_mod.json'; +import rule145 from './defense_evasion_iis_httplogging_disabled.json'; +import rule146 from './execution_python_tty_shell.json'; +import rule147 from './execution_perl_tty_shell.json'; +import rule148 from './defense_evasion_base16_or_base32_encoding_or_decoding_activity.json'; +import rule149 from './defense_evasion_base64_encoding_or_decoding_activity.json'; +import rule150 from './defense_evasion_hex_encoding_or_decoding_activity.json'; +import rule151 from './defense_evasion_file_mod_writable_dir.json'; +import rule152 from './defense_evasion_disable_selinux_attempt.json'; +import rule153 from './discovery_kernel_module_enumeration.json'; +import rule154 from './lateral_movement_telnet_network_activity_external.json'; +import rule155 from './lateral_movement_telnet_network_activity_internal.json'; +import rule156 from './privilege_escalation_setuid_setgid_bit_set_via_chmod.json'; +import rule157 from './defense_evasion_attempt_to_disable_iptables_or_firewall.json'; +import rule158 from './defense_evasion_kernel_module_removal.json'; +import rule159 from './defense_evasion_attempt_to_disable_syslog_service.json'; +import rule160 from './defense_evasion_file_deletion_via_shred.json'; +import rule161 from './discovery_virtual_machine_fingerprinting.json'; +import rule162 from './defense_evasion_hidden_file_dir_tmp.json'; +import rule163 from './defense_evasion_deletion_of_bash_command_line_history.json'; +import rule164 from './impact_cloudwatch_log_group_deletion.json'; +import rule165 from './impact_cloudwatch_log_stream_deletion.json'; +import rule166 from './impact_rds_instance_cluster_stoppage.json'; +import rule167 from './persistence_attempt_to_deactivate_mfa_for_okta_user_account.json'; +import rule168 from './persistence_rds_cluster_creation.json'; +import rule169 from './credential_access_attempted_bypass_of_okta_mfa.json'; +import rule170 from './defense_evasion_waf_acl_deletion.json'; +import rule171 from './impact_attempt_to_revoke_okta_api_token.json'; +import rule172 from './impact_iam_group_deletion.json'; +import rule173 from './impact_possible_okta_dos_attack.json'; +import rule174 from './impact_rds_cluster_deletion.json'; +import rule175 from './initial_access_suspicious_activity_reported_by_okta_user.json'; +import rule176 from './okta_attempt_to_deactivate_okta_policy.json'; +import rule177 from './okta_attempt_to_deactivate_okta_policy_rule.json'; +import rule178 from './okta_attempt_to_modify_okta_network_zone.json'; +import rule179 from './okta_attempt_to_modify_okta_policy.json'; +import rule180 from './okta_attempt_to_modify_okta_policy_rule.json'; +import rule181 from './okta_threat_detected_by_okta_threatinsight.json'; +import rule182 from './persistence_administrator_privileges_assigned_to_okta_group.json'; +import rule183 from './persistence_attempt_to_create_okta_api_token.json'; +import rule184 from './persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json'; +import rule185 from './defense_evasion_cloudtrail_logging_deleted.json'; +import rule186 from './defense_evasion_ec2_network_acl_deletion.json'; +import rule187 from './impact_iam_deactivate_mfa_device.json'; +import rule188 from './defense_evasion_s3_bucket_configuration_deletion.json'; +import rule189 from './defense_evasion_guardduty_detector_deletion.json'; +import rule190 from './okta_attempt_to_delete_okta_policy.json'; +import rule191 from './credential_access_iam_user_addition_to_group.json'; +import rule192 from './persistence_ec2_network_acl_creation.json'; +import rule193 from './impact_ec2_disable_ebs_encryption.json'; +import rule194 from './persistence_iam_group_creation.json'; +import rule195 from './defense_evasion_waf_rule_or_rule_group_deletion.json'; +import rule196 from './collection_cloudtrail_logging_created.json'; +import rule197 from './defense_evasion_cloudtrail_logging_suspended.json'; +import rule198 from './impact_cloudtrail_logging_updated.json'; +import rule199 from './initial_access_console_login_root.json'; +import rule200 from './defense_evasion_cloudwatch_alarm_deletion.json'; +import rule201 from './defense_evasion_ec2_flow_log_deletion.json'; +import rule202 from './defense_evasion_configuration_recorder_stopped.json'; +import rule203 from './exfiltration_ec2_snapshot_change_activity.json'; +import rule204 from './defense_evasion_config_service_rule_deletion.json'; +import rule205 from './okta_attempt_to_modify_or_delete_application_sign_on_policy.json'; +import rule206 from './command_and_control_download_rar_powershell_from_internet.json'; +import rule207 from './initial_access_password_recovery.json'; +import rule208 from './command_and_control_cobalt_strike_beacon.json'; +import rule209 from './command_and_control_fin7_c2_behavior.json'; +import rule210 from './command_and_control_halfbaked_beacon.json'; +import rule211 from './credential_access_secretsmanager_getsecretvalue.json'; +import rule212 from './initial_access_via_system_manager.json'; +import rule213 from './privilege_escalation_root_login_without_mfa.json'; +import rule214 from './privilege_escalation_updateassumerolepolicy.json'; +import rule215 from './impact_hosts_file_modified.json'; +import rule216 from './elastic_endpoint.json'; +import rule217 from './external_alerts.json'; +import rule218 from './initial_access_login_failures.json'; +import rule219 from './initial_access_login_location.json'; +import rule220 from './initial_access_login_sessions.json'; +import rule221 from './initial_access_login_time.json'; +import rule222 from './ml_cloudtrail_error_message_spike.json'; +import rule223 from './ml_cloudtrail_rare_error_code.json'; +import rule224 from './ml_cloudtrail_rare_method_by_city.json'; +import rule225 from './ml_cloudtrail_rare_method_by_country.json'; +import rule226 from './ml_cloudtrail_rare_method_by_user.json'; +import rule227 from './credential_access_aws_iam_assume_role_brute_force.json'; +import rule228 from './credential_access_okta_brute_force_or_password_spraying.json'; +import rule229 from './initial_access_unusual_dns_service_children.json'; +import rule230 from './initial_access_unusual_dns_service_file_writes.json'; +import rule231 from './lateral_movement_dns_server_overflow.json'; +import rule232 from './credential_access_root_console_failure_brute_force.json'; +import rule233 from './initial_access_unsecure_elasticsearch_node.json'; +import rule234 from './credential_access_domain_backup_dpapi_private_keys.json'; +import rule235 from './persistence_gpo_schtask_service_creation.json'; +import rule236 from './credential_access_credentials_keychains.json'; +import rule237 from './credential_access_kerberosdump_kcc.json'; +import rule238 from './defense_evasion_attempt_del_quarantine_attrib.json'; +import rule239 from './execution_suspicious_psexesvc.json'; +import rule240 from './execution_via_xp_cmdshell_mssql_stored_procedure.json'; +import rule241 from './privilege_escalation_printspooler_service_suspicious_file.json'; +import rule242 from './privilege_escalation_printspooler_suspicious_spl_file.json'; +import rule243 from './defense_evasion_azure_diagnostic_settings_deletion.json'; +import rule244 from './execution_command_virtual_machine.json'; +import rule245 from './execution_via_hidden_shell_conhost.json'; +import rule246 from './impact_resource_group_deletion.json'; +import rule247 from './persistence_via_telemetrycontroller_scheduledtask_hijack.json'; +import rule248 from './persistence_via_update_orchestrator_service_hijack.json'; +import rule249 from './collection_update_event_hub_auth_rule.json'; +import rule250 from './credential_access_iis_apppoolsa_pwd_appcmd.json'; +import rule251 from './credential_access_iis_connectionstrings_dumping.json'; +import rule252 from './defense_evasion_event_hub_deletion.json'; +import rule253 from './defense_evasion_firewall_policy_deletion.json'; +import rule254 from './defense_evasion_sdelete_like_filename_rename.json'; +import rule255 from './lateral_movement_remote_ssh_login_enabled.json'; +import rule256 from './persistence_azure_automation_account_created.json'; +import rule257 from './persistence_azure_automation_runbook_created_or_modified.json'; +import rule258 from './persistence_azure_automation_webhook_created.json'; +import rule259 from './privilege_escalation_uac_bypass_diskcleanup_hijack.json'; +import rule260 from './credential_access_attempts_to_brute_force_okta_user_account.json'; +import rule261 from './credential_access_storage_account_key_regenerated.json'; +import rule262 from './defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json'; +import rule263 from './defense_evasion_system_critical_proc_abnormal_file_activity.json'; +import rule264 from './defense_evasion_unusual_system_vp_child_program.json'; +import rule265 from './discovery_blob_container_access_mod.json'; +import rule266 from './persistence_mfa_disabled_for_azure_user.json'; +import rule267 from './persistence_user_added_as_owner_for_azure_application.json'; +import rule268 from './persistence_user_added_as_owner_for_azure_service_principal.json'; +import rule269 from './defense_evasion_dotnet_compiler_parent_process.json'; +import rule270 from './defense_evasion_suspicious_managedcode_host_process.json'; +import rule271 from './execution_command_shell_started_by_unusual_process.json'; +import rule272 from './defense_evasion_masquerading_as_elastic_endpoint_process.json'; +import rule273 from './defense_evasion_masquerading_suspicious_werfault_childproc.json'; +import rule274 from './defense_evasion_masquerading_werfault.json'; +import rule275 from './credential_access_key_vault_modified.json'; +import rule276 from './credential_access_mimikatz_memssp_default_logs.json'; +import rule277 from './defense_evasion_code_injection_conhost.json'; +import rule278 from './defense_evasion_network_watcher_deletion.json'; +import rule279 from './initial_access_external_guest_user_invite.json'; +import rule280 from './defense_evasion_masquerading_renamed_autoit.json'; +import rule281 from './impact_azure_automation_runbook_deleted.json'; +import rule282 from './initial_access_consent_grant_attack_via_azure_registered_application.json'; +import rule283 from './persistence_azure_conditional_access_policy_modified.json'; +import rule284 from './persistence_azure_privileged_identity_management_role_modified.json'; +import rule285 from './command_and_control_teamviewer_remote_file_copy.json'; +import rule286 from './defense_evasion_installutil_beacon.json'; +import rule287 from './defense_evasion_mshta_beacon.json'; +import rule288 from './defense_evasion_network_connection_from_windows_binary.json'; +import rule289 from './defense_evasion_rundll32_no_arguments.json'; +import rule290 from './defense_evasion_suspicious_scrobj_load.json'; +import rule291 from './defense_evasion_suspicious_wmi_script.json'; +import rule292 from './execution_ms_office_written_file.json'; +import rule293 from './execution_pdf_written_file.json'; +import rule294 from './lateral_movement_cmd_service.json'; +import rule295 from './persistence_app_compat_shim.json'; +import rule296 from './command_and_control_remote_file_copy_desktopimgdownldr.json'; +import rule297 from './command_and_control_remote_file_copy_mpcmdrun.json'; +import rule298 from './defense_evasion_execution_suspicious_explorer_winword.json'; +import rule299 from './defense_evasion_suspicious_zoom_child_process.json'; +import rule300 from './ml_linux_anomalous_compiler_activity.json'; +import rule301 from './ml_linux_anomalous_kernel_module_arguments.json'; +import rule302 from './ml_linux_anomalous_sudo_activity.json'; +import rule303 from './ml_linux_system_information_discovery.json'; +import rule304 from './ml_linux_system_network_configuration_discovery.json'; +import rule305 from './ml_linux_system_network_connection_discovery.json'; +import rule306 from './ml_linux_system_process_discovery.json'; +import rule307 from './ml_linux_system_user_discovery.json'; +import rule308 from './discovery_post_exploitation_public_ip_reconnaissance.json'; +import rule309 from './initial_access_zoom_meeting_with_no_passcode.json'; +import rule310 from './defense_evasion_gcp_logging_sink_deletion.json'; +import rule311 from './defense_evasion_gcp_pub_sub_topic_deletion.json'; +import rule312 from './defense_evasion_gcp_firewall_rule_created.json'; +import rule313 from './defense_evasion_gcp_firewall_rule_deleted.json'; +import rule314 from './defense_evasion_gcp_firewall_rule_modified.json'; +import rule315 from './defense_evasion_gcp_logging_bucket_deletion.json'; +import rule316 from './defense_evasion_gcp_storage_bucket_permissions_modified.json'; +import rule317 from './impact_gcp_storage_bucket_deleted.json'; +import rule318 from './initial_access_gcp_iam_custom_role_creation.json'; +import rule319 from './persistence_gcp_iam_service_account_key_deletion.json'; +import rule320 from './persistence_gcp_key_created_for_service_account.json'; +import rule321 from './defense_evasion_gcp_storage_bucket_configuration_modified.json'; +import rule322 from './exfiltration_gcp_logging_sink_modification.json'; +import rule323 from './impact_gcp_iam_role_deletion.json'; +import rule324 from './impact_gcp_service_account_deleted.json'; +import rule325 from './impact_gcp_service_account_disabled.json'; +import rule326 from './impact_gcp_virtual_private_cloud_network_deleted.json'; +import rule327 from './impact_gcp_virtual_private_cloud_route_created.json'; +import rule328 from './impact_gcp_virtual_private_cloud_route_deleted.json'; +import rule329 from './ml_linux_anomalous_metadata_process.json'; +import rule330 from './ml_linux_anomalous_metadata_user.json'; +import rule331 from './ml_windows_anomalous_metadata_process.json'; +import rule332 from './ml_windows_anomalous_metadata_user.json'; +import rule333 from './persistence_gcp_service_account_created.json'; +import rule334 from './collection_gcp_pub_sub_subscription_creation.json'; +import rule335 from './collection_gcp_pub_sub_topic_creation.json'; +import rule336 from './defense_evasion_gcp_pub_sub_subscription_deletion.json'; +import rule337 from './persistence_azure_pim_user_added_global_admin.json'; +import rule338 from './command_and_control_cobalt_strike_default_teamserver_cert.json'; +import rule339 from './defense_evasion_enable_inbound_rdp_with_netsh.json'; +import rule340 from './defense_evasion_execution_lolbas_wuauclt.json'; +import rule341 from './privilege_escalation_unusual_svchost_childproc_childless.json'; +import rule342 from './lateral_movement_rdp_tunnel_plink.json'; +import rule343 from './privilege_escalation_uac_bypass_winfw_mmc_hijack.json'; +import rule344 from './persistence_ms_office_addins_file.json'; +import rule345 from './discovery_adfind_command_activity.json'; +import rule346 from './discovery_security_software_wmic.json'; +import rule347 from './execution_command_shell_via_rundll32.json'; +import rule348 from './execution_suspicious_cmd_wmi.json'; +import rule349 from './lateral_movement_via_startup_folder_rdp_smb.json'; +import rule350 from './privilege_escalation_uac_bypass_com_interface_icmluautil.json'; +import rule351 from './privilege_escalation_uac_bypass_mock_windir.json'; +import rule352 from './defense_evasion_potential_processherpaderping.json'; +import rule353 from './privilege_escalation_uac_bypass_dll_sideloading.json'; +import rule354 from './execution_shared_modules_local_sxs_dll.json'; +import rule355 from './privilege_escalation_uac_bypass_com_clipup.json'; +import rule356 from './initial_access_via_explorer_suspicious_child_parent_args.json'; +import rule357 from './execution_from_unusual_directory.json'; +import rule358 from './execution_from_unusual_path_cmdline.json'; +import rule359 from './credential_access_kerberoasting_unusual_process.json'; +import rule360 from './discovery_peripheral_device.json'; +import rule361 from './lateral_movement_mount_hidden_or_webdav_share_net.json'; +import rule362 from './defense_evasion_deleting_websvr_access_logs.json'; +import rule363 from './defense_evasion_log_files_deleted.json'; +import rule364 from './defense_evasion_timestomp_touch.json'; +import rule365 from './lateral_movement_dcom_hta.json'; +import rule366 from './lateral_movement_execution_via_file_shares_sequence.json'; +import rule367 from './privilege_escalation_uac_bypass_com_ieinstal.json'; +import rule368 from './command_and_control_common_webservices.json'; +import rule369 from './command_and_control_encrypted_channel_freesslcert.json'; +import rule370 from './defense_evasion_process_termination_followed_by_deletion.json'; +import rule371 from './lateral_movement_remote_file_copy_hidden_share.json'; +import rule372 from './attempt_to_deactivate_okta_network_zone.json'; +import rule373 from './attempt_to_delete_okta_network_zone.json'; +import rule374 from './lateral_movement_dcom_mmc20.json'; +import rule375 from './lateral_movement_dcom_shellwindow_shellbrowserwindow.json'; +import rule376 from './okta_attempt_to_deactivate_okta_application.json'; +import rule377 from './okta_attempt_to_delete_okta_application.json'; +import rule378 from './okta_attempt_to_delete_okta_policy_rule.json'; +import rule379 from './okta_attempt_to_modify_okta_application.json'; +import rule380 from './persistence_administrator_role_assigned_to_okta_user.json'; +import rule381 from './lateral_movement_executable_tool_transfer_smb.json'; +import rule382 from './command_and_control_dns_tunneling_nslookup.json'; +import rule383 from './lateral_movement_execution_from_tsclient_mup.json'; +import rule384 from './lateral_movement_rdp_sharprdp_target.json'; +import rule385 from './defense_evasion_clearing_windows_security_logs.json'; +import rule386 from './persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json'; +import rule387 from './execution_suspicious_short_program_name.json'; +import rule388 from './lateral_movement_incoming_wmi.json'; +import rule389 from './persistence_via_hidden_run_key_valuename.json'; +import rule390 from './credential_access_potential_ssh_bruteforce.json'; +import rule391 from './credential_access_promt_for_pwd_via_osascript.json'; +import rule392 from './lateral_movement_remote_services.json'; +import rule393 from './application_added_to_google_workspace_domain.json'; +import rule394 from './domain_added_to_google_workspace_trusted_domains.json'; +import rule395 from './execution_suspicious_image_load_wmi_ms_office.json'; +import rule396 from './execution_suspicious_powershell_imgload.json'; +import rule397 from './google_workspace_admin_role_deletion.json'; +import rule398 from './google_workspace_mfa_enforcement_disabled.json'; +import rule399 from './google_workspace_policy_modified.json'; +import rule400 from './mfa_disabled_for_google_workspace_organization.json'; +import rule401 from './persistence_evasion_registry_ifeo_injection.json'; +import rule402 from './persistence_google_workspace_admin_role_assigned_to_user.json'; +import rule403 from './persistence_google_workspace_custom_admin_role_created.json'; +import rule404 from './persistence_google_workspace_role_modified.json'; +import rule405 from './persistence_suspicious_image_load_scheduled_task_ms_office.json'; +import rule406 from './defense_evasion_masquerading_trusted_directory.json'; +import rule407 from './exfiltration_microsoft_365_exchange_transport_rule_creation.json'; +import rule408 from './initial_access_microsoft_365_exchange_safelinks_disabled.json'; +import rule409 from './microsoft_365_exchange_dkim_signing_config_disabled.json'; +import rule410 from './persistence_appcertdlls_registry.json'; +import rule411 from './persistence_appinitdlls_registry.json'; +import rule412 from './persistence_registry_uncommon.json'; +import rule413 from './persistence_run_key_and_startup_broad.json'; +import rule414 from './persistence_services_registry.json'; +import rule415 from './persistence_startup_folder_file_written_by_suspicious_process.json'; +import rule416 from './persistence_startup_folder_scripts.json'; +import rule417 from './persistence_suspicious_com_hijack_registry.json'; +import rule418 from './persistence_via_lsa_security_support_provider_registry.json'; +import rule419 from './defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json'; +import rule420 from './defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json'; +import rule421 from './defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json'; +import rule422 from './exfiltration_microsoft_365_exchange_transport_rule_mod.json'; +import rule423 from './initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json'; +import rule424 from './initial_access_microsoft_365_exchange_anti_phish_rule_mod.json'; +import rule425 from './lateral_movement_suspicious_rdp_client_imageload.json'; +import rule426 from './persistence_runtime_run_key_startup_susp_procs.json'; +import rule427 from './persistence_suspicious_scheduled_task_runtime.json'; +import rule428 from './defense_evasion_microsoft_365_exchange_dlp_policy_removed.json'; +import rule429 from './lateral_movement_scheduled_task_target.json'; +import rule430 from './persistence_microsoft_365_exchange_management_role_assignment.json'; +import rule431 from './persistence_microsoft_365_teams_guest_access_enabled.json'; +import rule432 from './credential_access_dump_registry_hives.json'; +import rule433 from './defense_evasion_scheduledjobs_at_protocol_enabled.json'; +import rule434 from './persistence_ms_outlook_vba_template.json'; +import rule435 from './persistence_suspicious_service_created_registry.json'; +import rule436 from './privilege_escalation_named_pipe_impersonation.json'; +import rule437 from './credential_access_cmdline_dump_tool.json'; +import rule438 from './credential_access_copy_ntds_sam_volshadowcp_cmdline.json'; +import rule439 from './credential_access_lsass_memdump_file_created.json'; +import rule440 from './lateral_movement_incoming_winrm_shell_execution.json'; +import rule441 from './lateral_movement_powershell_remoting_target.json'; +import rule442 from './defense_evasion_hide_encoded_executable_registry.json'; +import rule443 from './defense_evasion_port_forwarding_added_registry.json'; +import rule444 from './lateral_movement_rdp_enabled_registry.json'; +import rule445 from './privilege_escalation_printspooler_registry_copyfiles.json'; +import rule446 from './privilege_escalation_rogue_windir_environment_var.json'; +import rule447 from './initial_access_scripts_process_started_via_wmi.json'; +import rule448 from './command_and_control_iexplore_via_com.json'; +import rule449 from './command_and_control_remote_file_copy_scripts.json'; +import rule450 from './persistence_local_scheduled_task_scripting.json'; +import rule451 from './persistence_startup_folder_file_written_by_unsigned_process.json'; +import rule452 from './command_and_control_remote_file_copy_powershell.json'; +import rule453 from './credential_access_microsoft_365_brute_force_user_account_attempt.json'; +import rule454 from './microsoft_365_teams_custom_app_interaction_allowed.json'; +import rule455 from './persistence_microsoft_365_teams_external_access_enabled.json'; +import rule456 from './credential_access_microsoft_365_potential_password_spraying_attack.json'; +import rule457 from './defense_evasion_stop_process_service_threshold.json'; +import rule458 from './collection_winrar_encryption.json'; +import rule459 from './defense_evasion_unusual_dir_ads.json'; +import rule460 from './discovery_admin_recon.json'; +import rule461 from './discovery_file_dir_discovery.json'; +import rule462 from './discovery_net_view.json'; +import rule463 from './discovery_query_registry_via_reg.json'; +import rule464 from './discovery_remote_system_discovery_commands_windows.json'; +import rule465 from './persistence_via_windows_management_instrumentation_event_subscription.json'; +import rule466 from './execution_scripting_osascript_exec_followed_by_netcon.json'; +import rule467 from './execution_shell_execution_via_apple_scripting.json'; +import rule468 from './persistence_creation_change_launch_agents_file.json'; +import rule469 from './persistence_creation_modif_launch_deamon_sequence.json'; +import rule470 from './persistence_folder_action_scripts_runtime.json'; +import rule471 from './persistence_login_logout_hooks_defaults.json'; +import rule472 from './privilege_escalation_explicit_creds_via_scripting.json'; +import rule473 from './command_and_control_sunburst_c2_activity_detected.json'; +import rule474 from './defense_evasion_azure_application_credential_modification.json'; +import rule475 from './defense_evasion_azure_service_principal_addition.json'; +import rule476 from './defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json'; +import rule477 from './execution_apt_solarwinds_backdoor_child_cmd_powershell.json'; +import rule478 from './execution_apt_solarwinds_backdoor_unusual_child_processes.json'; +import rule479 from './initial_access_azure_active_directory_powershell_signin.json'; +import rule480 from './collection_email_powershell_exchange_mailbox.json'; +import rule481 from './collection_persistence_powershell_exch_mailbox_activesync_add_device.json'; +import rule482 from './execution_scheduled_task_powershell_source.json'; +import rule483 from './persistence_docker_shortcuts_plist_modification.json'; +import rule484 from './persistence_evasion_hidden_local_account_creation.json'; +import rule485 from './persistence_finder_sync_plugin_pluginkit.json'; +import rule486 from './discovery_security_software_grep.json'; +import rule487 from './credential_access_cookies_chromium_browsers_debugging.json'; +import rule488 from './credential_access_ssh_backdoor_log.json'; +import rule489 from './persistence_credential_access_modify_auth_module_or_config.json'; +import rule490 from './persistence_credential_access_modify_ssh_binaries.json'; +import rule491 from './credential_access_collection_sensitive_files.json'; +import rule492 from './persistence_ssh_authorized_keys_modification.json'; +import rule493 from './defense_evasion_defender_disabled_via_registry.json'; +import rule494 from './defense_evasion_privacy_controls_tcc_database_modification.json'; +import rule495 from './execution_initial_access_suspicious_browser_childproc.json'; +import rule496 from './execution_script_via_automator_workflows.json'; +import rule497 from './persistence_modification_sublime_app_plugin_or_script.json'; +import rule498 from './privilege_escalation_applescript_with_admin_privs.json'; +import rule499 from './credential_access_dumping_keychain_security.json'; +import rule500 from './initial_access_azure_active_directory_high_risk_signin.json'; +import rule501 from './initial_access_suspicious_mac_ms_office_child_process.json'; +import rule502 from './credential_access_mitm_localhost_webproxy.json'; +import rule503 from './persistence_kde_autostart_modification.json'; +import rule504 from './persistence_user_account_added_to_privileged_group_ad.json'; +import rule505 from './defense_evasion_attempt_to_disable_gatekeeper.json'; +import rule506 from './defense_evasion_sandboxed_office_app_suspicious_zip_file.json'; +import rule507 from './persistence_emond_rules_file_creation.json'; +import rule508 from './persistence_emond_rules_process_execution.json'; +import rule509 from './discovery_users_domain_built_in_commands.json'; +import rule510 from './execution_pentest_eggshell_remote_admin_tool.json'; +import rule511 from './defense_evasion_install_root_certificate.json'; +import rule512 from './persistence_credential_access_authorization_plugin_creation.json'; +import rule513 from './persistence_directory_services_plugins_modification.json'; +import rule514 from './defense_evasion_modify_environment_launchctl.json'; +import rule515 from './defense_evasion_safari_config_change.json'; +import rule516 from './defense_evasion_apple_softupdates_modification.json'; +import rule517 from './persistence_cron_jobs_creation_and_runtime.json'; +import rule518 from './credential_access_mod_wdigest_security_provider.json'; +import rule519 from './credential_access_saved_creds_vaultcmd.json'; +import rule520 from './defense_evasion_file_creation_mult_extension.json'; +import rule521 from './execution_enumeration_via_wmiprvse.json'; +import rule522 from './execution_suspicious_jar_child_process.json'; +import rule523 from './persistence_shell_profile_modification.json'; +import rule524 from './persistence_suspicious_calendar_modification.json'; +import rule525 from './persistence_time_provider_mod.json'; +import rule526 from './privilege_escalation_exploit_adobe_acrobat_updater.json'; +import rule527 from './defense_evasion_sip_provider_mod.json'; +import rule528 from './execution_com_object_xwizard.json'; +import rule529 from './privilege_escalation_disable_uac_registry.json'; +import rule530 from './defense_evasion_unusual_ads_file_creation.json'; +import rule531 from './persistence_loginwindow_plist_modification.json'; +import rule532 from './persistence_periodic_tasks_file_mdofiy.json'; +import rule533 from './persistence_via_atom_init_file_modification.json'; +import rule534 from './privilege_escalation_lsa_auth_package.json'; +import rule535 from './privilege_escalation_port_monitor_print_pocessor_abuse.json'; +import rule536 from './credential_access_dumping_hashes_bi_cmds.json'; +import rule537 from './lateral_movement_mounting_smb_share.json'; +import rule538 from './privilege_escalation_echo_nopasswd_sudoers.json'; +import rule539 from './privilege_escalation_ld_preload_shared_object_modif.json'; +import rule540 from './privilege_escalation_root_crontab_filemod.json'; +import rule541 from './defense_evasion_create_mod_root_certificate.json'; +import rule542 from './privilege_escalation_sudo_buffer_overflow.json'; export const rawRules = [ rule1, @@ -934,4 +1015,85 @@ export const rawRules = [ rule459, rule460, rule461, + rule462, + rule463, + rule464, + rule465, + rule466, + rule467, + rule468, + rule469, + rule470, + rule471, + rule472, + rule473, + rule474, + rule475, + rule476, + rule477, + rule478, + rule479, + rule480, + rule481, + rule482, + rule483, + rule484, + rule485, + rule486, + rule487, + rule488, + rule489, + rule490, + rule491, + rule492, + rule493, + rule494, + rule495, + rule496, + rule497, + rule498, + rule499, + rule500, + rule501, + rule502, + rule503, + rule504, + rule505, + rule506, + rule507, + rule508, + rule509, + rule510, + rule511, + rule512, + rule513, + rule514, + rule515, + rule516, + rule517, + rule518, + rule519, + rule520, + rule521, + rule522, + rule523, + rule524, + rule525, + rule526, + rule527, + rule528, + rule529, + rule530, + rule531, + rule532, + rule533, + rule534, + rule535, + rule536, + rule537, + rule538, + rule539, + rule540, + rule541, + rule542, ]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json new file mode 100644 index 00000000000000..230c5e33b75610 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic", + "Willem D'Haese" + ], + "description": "Identifies high risk Azure Active Directory (AD) sign-ins by leveraging Microsoft's Identity Protection machine learning and heuristics. Identity Protection categorizes risk into three tiers: low, medium, and high. While Microsoft does not provide specific details about how risk is calculated, each level brings higher confidence that the user or sign-in is compromised.", + "from": "now-25m", + "index": [ + "filebeat-*", + "logs-azure.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Azure Active Directory High Risk Sign-in", + "note": "The Azure Fleet Integration or Filebeat module must be enabled to use this rule.", + "query": "event.dataset:azure.signinlogs and azure.signinlogs.properties.risk_level_during_signin:high and event.outcome:(success or Success)", + "references": [ + "https://docs.microsoft.com/en-us/azure/active-directory/conditional-access/howto-conditional-access-policy-risk", + "https://docs.microsoft.com/en-us/azure/active-directory/identity-protection/overview-identity-protection", + "https://docs.microsoft.com/en-us/azure/active-directory/identity-protection/howto-identity-protection-investigate-risk" + ], + "risk_score": 73, + "rule_id": "37994bca-0611-4500-ab67-5588afe73b77", + "severity": "high", + "tags": [ + "Elastic", + "Cloud", + "Azure", + "Continuous Monitoring", + "SecOps", + "Identity and Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_failures.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_failures.json new file mode 100644 index 00000000000000..e825c1b14e30cc --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_failures.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies that the maximum number of failed login attempts has been reached for a user.", + "index": [ + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Auditd Max Failed Login Attempts", + "query": "event.module:auditd and event.action:\"failed-log-in-too-many-times-to\"", + "references": [ + "https://github.com/linux-pam/linux-pam/blob/0adbaeb273da1d45213134aa271e95987103281c/modules/pam_faillock/pam_faillock.c#L574" + ], + "risk_score": 47, + "rule_id": "fb9937ce-7e21-46bf-831d-1ad96eac674d", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Initial Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_location.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_location.json new file mode 100644 index 00000000000000..81599a9c524a09 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_location.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies that a login attempt has happened from a forbidden location.", + "index": [ + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Auditd Login from Forbidden Location", + "query": "event.module:auditd and event.action:\"attempted-log-in-from-unusual-place-to\"", + "references": [ + "https://github.com/linux-pam/linux-pam/blob/aac5a8fdc4aa3f7e56335a6343774cc1b63b408d/modules/pam_access/pam_access.c#L412" + ], + "risk_score": 73, + "rule_id": "cab4f01c-793f-4a54-a03e-e5d85b96d7af", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Initial Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_sessions.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_sessions.json new file mode 100644 index 00000000000000..2c885590925159 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_sessions.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies that the maximum number login sessions has been reached for a user.", + "index": [ + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Auditd Max Login Sessions", + "query": "event.module:auditd and event.action:\"opened-too-many-sessions-to\"", + "references": [ + "https://github.com/linux-pam/linux-pam/blob/70c32cc6fca51338f92afa58eb75b1107a5c2430/modules/pam_limits/pam_limits.c#L1007" + ], + "risk_score": 47, + "rule_id": "20dc4620-3b68-4269-8124-ca5091e00ea8", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Initial Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_time.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_time.json new file mode 100644 index 00000000000000..ca343e7ef9cc09 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_login_time.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies that a login attempt occurred at a forbidden time.", + "index": [ + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Auditd Login Attempt at Forbidden Time", + "query": "event.module:auditd and event.action:\"attempted-log-in-during-unusual-hour-to\"", + "references": [ + "https://github.com/linux-pam/linux-pam/blob/aac5a8fdc4aa3f7e56335a6343774cc1b63b408d/modules/pam_time/pam_time.c#L666" + ], + "risk_score": 47, + "rule_id": "90e28af7-1d96-4582-bf11-9a1eff21d0e5", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Initial Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rdp_remote_desktop_protocol_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rdp_remote_desktop_protocol_to_the_internet.json index 05ab69a05f7dcd..024541d71b6d36 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rdp_remote_desktop_protocol_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rdp_remote_desktop_protocol_to_the_internet.json @@ -6,6 +6,7 @@ "false_positives": [ "RDP connections may be made directly to Internet destinations in order to access Windows cloud server instances but such connections are usually made only by engineers. In such cases, only RDP gateways, bastions or jump servers may be expected Internet destinations and can be exempted from this rule. RDP may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." ], + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -59,5 +60,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json index 36db52a656d289..7119e5a587c69a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "This rule detects network events that may indicate the use of RPC traffic from the Internet. RPC is commonly used by system administrators to remotely control a system for maintenance or to use shared resources. It should almost never be directly exposed to the Internet, as it is frequently targeted and exploited by threat actors as an initial access or back-door vector.", + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json index 8ba074438cf50a..6da4f9f55071f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "This rule detects network events that may indicate the use of RPC traffic to the Internet. RPC is commonly used by system administrators to remotely control a system for maintenance or to use shared resources. It should almost never be directly exposed to the Internet, as it is frequently targeted and exploited by threat actors as an initial access or back-door vector.", + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json index 543aa2bcf6da13..7caa99711275b7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json index 8376bb7e62bd8b..1e7160eefdaba8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Windows Script Interpreter Executing Process via WMI", - "query": "sequence by host.id with maxspan=5s\n [library where file.name : \"wmiutils.dll\" and process.name : (\"wscript.exe\", \"cscript.exe\")]\n [process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"wmiprvse.exe\" and\n user.domain != \"NT AUTHORITY\" and\n (process.pe.original_file_name in\n (\n \"cscript.exe\",\n \"wscript.exe\",\n \"PowerShell.EXE\",\n \"Cmd.Exe\",\n \"MSHTA.EXE\",\n \"RUNDLL32.EXE\",\n \"REGSVR32.EXE\",\n \"MSBuild.exe\",\n \"InstallUtil.exe\",\n \"RegAsm.exe\",\n \"RegSvcs.exe\",\n \"msxsl.exe\",\n \"CONTROL.EXE\",\n \"EXPLORER.EXE\",\n \"Microsoft.Workflow.Compiler.exe\",\n \"msiexec.exe\"\n ) or\n process.executable : (\"C:\\\\Users\\\\*.exe\", \"C:\\\\ProgramData\\\\*.exe\")\n )\n ]\n", + "query": "sequence by host.id with maxspan = 5s\n [library where dll.name : \"wmiutils.dll\" and process.name : (\"wscript.exe\", \"cscript.exe\")]\n [process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"wmiprvse.exe\" and\n user.domain != \"NT AUTHORITY\" and\n (process.pe.original_file_name :\n (\n \"cscript.exe\",\n \"wscript.exe\",\n \"PowerShell.EXE\",\n \"Cmd.Exe\",\n \"MSHTA.EXE\",\n \"RUNDLL32.EXE\",\n \"REGSVR32.EXE\",\n \"MSBuild.exe\",\n \"InstallUtil.exe\",\n \"RegAsm.exe\",\n \"RegSvcs.exe\",\n \"msxsl.exe\",\n \"CONTROL.EXE\",\n \"EXPLORER.EXE\",\n \"Microsoft.Workflow.Compiler.exe\",\n \"msiexec.exe\"\n ) or\n process.executable : (\"C:\\\\Users\\\\*.exe\", \"C:\\\\ProgramData\\\\*.exe\")\n )\n ]\n", "risk_score": 47, "rule_id": "b64b183e-1a76-422d-9179-7b389513e74d", "severity": "medium", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json index aefcfa4e692715..4b480cf9b054f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "This rule detects network events that may indicate the use of Windows file sharing (also called SMB or CIFS) traffic to the Internet. SMB is commonly used within networks to share files, printers, and other system resources amongst trusted systems. It should almost never be directly exposed to the Internet, as it is frequently targeted and exploited by threat actors as an initial access or back-door vector or for data exfiltration.", + "from": "now-9m", "index": [ "filebeat-*", "packetbeat-*", @@ -56,5 +57,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_mac_ms_office_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_mac_ms_office_child_process.json new file mode 100644 index 00000000000000..638db6727a7265 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_mac_ms_office_child_process.json @@ -0,0 +1,54 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies suspicious child processes of frequently targeted Microsoft Office applications (Word, PowerPoint, and Excel). These child processes are often launched during exploitation of Office applications or by documents with malicious macros.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious macOS MS Office Child Process", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name:(\"Microsoft Word\", \"Microsoft PowerPoint\", \"Microsoft Excel\") and\n process.name:\n (\n \"bash\", \n \"dash\", \n \"sh\", \n \"tcsh\", \n \"csh\", \n \"zsh\", \n \"ksh\", \n \"fish\", \n \"python*\", \n \"perl*\", \n \"php*\", \n \"osascript\",\n \"pwsh\", \n \"curl\", \n \"wget\", \n \"cp\", \n \"mv\", \n \"base64\", \n \"launchctl\"\n )\n", + "references": [ + "https://blog.malwarebytes.com/cybercrime/2017/02/microsoft-office-macro-malware-targets-macs/" + ], + "risk_score": 47, + "rule_id": "66da12b1-ac83-40eb-814c-07ed1d82b7b9", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1566", + "name": "Phishing", + "reference": "https://attack.mitre.org/techniques/T1566/", + "subtechnique": [ + { + "id": "T1566.001", + "name": "Spearphishing Attachment", + "reference": "https://attack.mitre.org/techniques/T1566/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json index 91dcfd13664093..1749be1142255f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json index 533996d75dcd4c..03c145779f3d51 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json index 6e5f0cb13417c2..4dad8ac892806c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json @@ -6,9 +6,11 @@ "false_positives": [ "Werfault.exe will legitimately spawn when dns.exe crashes, but the DNS service is very stable and so this is a low occurring event. Denial of Service (DoS) attempts by intentionally crashing the service will also cause werfault.exe to spawn." ], + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -49,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json index 7f73196493e48f..f8b58c5affe9e0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies an unexpected file being modified by dns.exe, the process responsible for Windows DNS Server services, which may indicate activity related to remote code execution or other forms of exploitation.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -45,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json index 8ef22da2319c9d..d88b8b06d09aab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious Explorer Child Process", "query": "process where event.type in (\"start\", \"process_started\") and\n process.name : (\"cscript.exe\", \"wscript.exe\", \"powershell.exe\", \"rundll32.exe\", \"cmd.exe\", \"mshta.exe\", \"regsvr32.exe\") and\n /* Explorer started via DCOM */\n process.parent.name : \"explorer.exe\" and process.parent.args : \"-Embedding\"\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "9a5b4e31-6cde-4295-9ff7-6be1b8567e1b", "severity": "medium", "tags": [ @@ -53,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_cmd_service.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_cmd_service.json index db35602753ab02..7e6aece1c359f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_cmd_service.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_cmd_service.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Service Command Lateral Movement", - "query": "sequence by process.entity_id with maxspan=1m\n [process where event.type in (\"start\", \"process_started\") and\n /* uncomment once in winlogbeat */\n (process.name == \"sc.exe\" /* or process.pe.original_file_name == \"sc.exe\" */ ) and\n /* case insensitive */\n wildcard(process.args, \"\\\\\\\\*\") and wildcard(process.args, \"binPath=*\", \"binpath=*\") and\n (process.args : \"create\" or\n process.args : \"config\" or\n process.args : \"failure\" or\n process.args : \"start\")]\n [network where process.name : \"sc.exe\" and destination.ip != \"127.0.0.1\"]\n", + "query": "sequence by process.entity_id with maxspan = 1m\n [process where event.type in (\"start\", \"process_started\") and\n (process.name : \"sc.exe\" or process.pe.original_file_name : \"sc.exe\") and\n process.args : \"\\\\\\\\*\" and process.args : (\"binPath=*\", \"binpath=*\") and\n process.args : (\"create\", \"config\", \"failure\", \"start\")]\n [network where process.name : \"sc.exe\" and destination.ip != \"127.0.0.1\"]\n", "risk_score": 21, "rule_id": "d61cbcf8-1bc1-4cff-85ba-e7b21c5beedc", "severity": "low", @@ -84,5 +85,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json new file mode 100644 index 00000000000000..467ebb045190bb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json @@ -0,0 +1,71 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies use of Bifrost, a known macOS Kerberos pentesting tool, which can be used to dump cached Kerberos tickets or attempt unauthorized authentication techniques such as pass-the-ticket/hash and kerberoasting.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Kerberos Attack via Bifrost", + "query": "event.category:process and event.type:start and process.args:(\"-action\" and (\"-kerberoast\" or askhash or asktgs or asktgt or s4u or (\"-ticket\" and ptt) or (dump and (tickets or keytab))))", + "references": [ + "https://github.com/its-a-feature/bifrost" + ], + "risk_score": 73, + "rule_id": "16904215-2c95-4ac8-bf5c-12354e047192", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Credential Access", + "Lateral Movement" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "technique": [ + { + "id": "T1550", + "name": "Use Alternate Authentication Material", + "reference": "https://attack.mitre.org/techniques/T1550/", + "subtechnique": [ + { + "id": "T1550.003", + "name": "Pass the Ticket", + "reference": "https://attack.mitre.org/techniques/T1550/003/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1558", + "name": "Steal or Forge Kerberos Tickets", + "reference": "https://attack.mitre.org/techniques/T1558/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json index 590f82e31b36b1..16069d546ba6ad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +44,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json index 5dee8493d57ad8..7aa04a808ca998 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +44,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json index 4e04dc1d458cb3..d8c4dfb5d2a73f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +44,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json index bb461cc8321e11..6e68d545d6672f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json index bc3f48904c43f5..6d82b129a3a707 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +41,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json index f3ce3a677bd295..b3eee1df24f772 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json index be4ad485fdbe48..81a9fc221a4a77 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -50,5 +51,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json index 0c9453940b3bc9..56f465b8b3b31e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +44,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json index e08c758f6f693e..9c7f4948a0cd11 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -49,5 +50,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_local_service_commands.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_local_service_commands.json index 3a04f62733a30b..21569be362998e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_local_service_commands.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_local_service_commands.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json index 0eade2646dcd8b..13d51e51636ede 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mounting_smb_share.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mounting_smb_share.json new file mode 100644 index 00000000000000..702d87b105f659 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mounting_smb_share.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of macOS built-in commands to mount a Server Message Block (SMB) network share. Adversaries may use valid accounts to interact with a remote network share using SMB.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Attempt to Mount SMB Share via Command Line", + "query": "process where event.type in (\"start\", \"process_started\") and\n (\n process.name : \"mount_smbfs\" or\n (process.name : \"open\" and process.args : \"smb://*\") or\n (process.name : \"mount\" and process.args : \"smbfs\") or\n (process.name : \"osascript\" and process.command_line : \"osascript*mount volume*smb://*\")\n )\n", + "references": [ + "https://www.freebsd.org/cgi/man.cgi?mount_smbfs", + "https://ss64.com/osx/mount.html" + ], + "risk_score": 21, + "rule_id": "661545b4-1a90-4f45-85ce-2ebd7c6a15d0", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Lateral Movement" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "technique": [ + { + "id": "T1021", + "name": "Remote Services", + "reference": "https://attack.mitre.org/techniques/T1021/", + "subtechnique": [ + { + "id": "T1021.002", + "name": "SMB/Windows Admin Shares", + "reference": "https://attack.mitre.org/techniques/T1021/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json index aae62e66ad2557..b384ed97c146dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -18,7 +19,7 @@ "references": [ "https://docs.microsoft.com/en-us/powershell/scripting/learn/remoting/running-remote-commands?view=powershell-7.1" ], - "risk_score": 43, + "risk_score": 47, "rule_id": "2772264c-6fb9-4d9d-9014-b416eed21254", "severity": "medium", "tags": [ @@ -46,5 +47,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json index 8c51622b0ab52c..f823fdb9908126 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "RDP Enabled via Registry", "query": "registry where\nregistry.path : \"HKLM\\\\SYSTEM\\\\ControlSet*\\\\Control\\\\Terminal Server\\\\fDenyTSConnections\" and\nregistry.data.strings == \"0\" and not (process.name : \"svchost.exe\" and user.domain == \"NT AUTHORITY\") and\nnot process.executable : \"C:\\\\Windows\\\\System32\\\\SystemPropertiesRemote.exe\"\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "58aa72ca-d968-4f34-b9f7-bea51d75eb50", "severity": "medium", "tags": [ @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json index 9a9a9d0e7a202d..3927c20bd54180 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -44,5 +45,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_tunnel_plink.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_tunnel_plink.json index 9f1ca61b4c62fe..b103b9962aec3a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_tunnel_plink.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_tunnel_plink.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Potential Remote Desktop Tunneling Detected", - "query": "process where event.type in (\"start\", \"process_started\", \"info\") and \n/* RDP port and usual SSH tunneling related switches in commandline */\nwildcard(process.args, \"*:3389\") and wildcard(process.args,\"-L\", \"-P\", \"-R\", \"-pw\", \"-ssh\")\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n /* RDP port and usual SSH tunneling related switches in command line */\n process.args : \"*:3389\" and\n process.args : (\"-L\", \"-P\", \"-R\", \"-pw\", \"-ssh\")\n", "references": [ "https://blog.netspi.com/how-to-access-rdp-over-a-reverse-ssh-tunnel/" ], @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json index 44d36351afbc96..d9427ca3415984 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json index 9d202cf61243d3..f6d612b73dc385 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -41,5 +42,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json index 109ed483653f55..42472277b20c55 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json @@ -11,10 +11,11 @@ "language": "kuery", "license": "Elastic License", "name": "Remote SSH Login Enabled via systemsetup Command", - "query": "event.category:process and event.type:(start or process_started) and process.name:systemsetup and process.args:(\"-f\" and \"-setremotelogin\" and on)", + "query": "event.category:process and event.type:(start or process_started) and process.name:systemsetup and process.args:(\"-setremotelogin\" and on)", "references": [ "https://documents.trendmicro.com/assets/pdf/XCSSET_Technical_Brief.pdf", - "https://ss64.com/osx/systemsetup.html" + "https://ss64.com/osx/systemsetup.html", + "https://support.apple.com/guide/remote-desktop/about-systemsetup-apd95406b8d/mac" ], "risk_score": 47, "rule_id": "5ae4e6f8-d1bf-40fa-96ba-e29645e1e4dc", @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json index ca29829849a0e5..245a6b4baf8d80 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -56,5 +57,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json index 522b57a1b7966e..9da368ca4e9140 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious RDP ActiveX Client Loaded", - "query": "library where file.name == \"mstscax.dll\" and\n /* depending on noise in your env add here extra paths */\n wildcard(process.executable, \"C:\\\\Windows\\\\*\",\n \"C:\\\\Users\\\\Public\\\\*\",\n \"C:\\\\Users\\\\Default\\\\*\",\n \"C:\\\\Intel\\\\*\",\n \"C:\\\\PerfLogs\\\\*\",\n \"C:\\\\ProgramData\\\\*\",\n \"\\\\Device\\\\Mup\\\\*\",\n \"\\\\\\\\*\") and\n /* add here FPs */\n not process.executable in (\"C:\\\\Windows\\\\System32\\\\mstsc.exe\", \"C:\\\\Windows\\\\SysWOW64\\\\mstsc.exe\")\n", + "query": "library where dll.name : \"mstscax.dll\" and\n /* depending on noise in your env add here extra paths */\n process.executable :\n (\n \"C:\\\\Windows\\\\*\",\n \"C:\\\\Users\\\\Public\\\\*\",\n \"C:\\\\Users\\\\Default\\\\*\",\n \"C:\\\\Intel\\\\*\",\n \"C:\\\\PerfLogs\\\\*\",\n \"C:\\\\ProgramData\\\\*\",\n \"\\\\Device\\\\Mup\\\\*\",\n \"\\\\\\\\*\"\n ) and\n /* add here FPs */\n not process.executable : (\"C:\\\\Windows\\\\System32\\\\mstsc.exe\", \"C:\\\\Windows\\\\SysWOW64\\\\mstsc.exe\")\n", "references": [ "https://posts.specterops.io/revisiting-remote-desktop-lateral-movement-8fb905cb46c3" ], @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json index c309f6dbdde29a..1f01a2c88fb09f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -66,5 +67,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_vpn_connection_attempt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_vpn_connection_attempt.json new file mode 100644 index 00000000000000..e3cd83fc44c04e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_vpn_connection_attempt.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of macOS built-in commands to connect to an existing Virtual Private Network (VPN).", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Virtual Private Network Connection Attempt", + "query": "process where event.type in (\"start\", \"process_started\") and\n (\n (process.name : \"networksetup\" and process.args : \"-connectpppoeservice\") or\n (process.name : \"scutil\" and process.args : \"--nc\" and process.args : \"start\") or\n (process.name : \"osascript\" and process.command_line : \"osascript*set VPN to service*\")\n )\n", + "references": [ + "https://github.com/rapid7/metasploit-framework/blob/master/modules/post/osx/manage/vpn.rb", + "https://www.unix.com/man-page/osx/8/networksetup/", + "https://superuser.com/questions/358513/start-configured-vpn-from-command-line-osx" + ], + "risk_score": 21, + "rule_id": "15dacaa0-5b90-466b-acab-63435a59701a", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Lateral Movement" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "technique": [ + { + "id": "T1021", + "name": "Remote Services", + "reference": "https://attack.mitre.org/techniques/T1021/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json new file mode 100644 index 00000000000000..3d1f1cf63d5b82 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to create a local account that will be hidden from the macOS logon window. This may indicate an attempt to evade user attention while maintaining persistence using a separate local account.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Hidden Local User Account Creation", + "query": "event.category:process and event.type:(start or process_started) and process.name:dscl and process.args:(IsHidden and create and (true or 1 or yes))", + "references": [ + "https://support.apple.com/en-us/HT203998" + ], + "risk_score": 47, + "rule_id": "41b638a1-8ab6-4f8e-86d9-466317ef2db5", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/", + "subtechnique": [ + { + "id": "T1078.003", + "name": "Local Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json index 5f569781c2d499..87e2a956888f22 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_app_compat_shim.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_app_compat_shim.json index 3e7bfb9f46ce5a..b0704dd78a51de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_app_compat_shim.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_app_compat_shim.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Installation of Custom Shim Databases", - "query": "sequence by process.entity_id with maxspan=5m\n [process where event.type in (\"start\", \"process_started\") and\n not (process.name : \"sdbinst.exe\" and process.parent.name : \"msiexec.exe\")]\n [registry where event.type in (\"creation\", \"change\") and\n wildcard(registry.path, \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\\\\Custom\\\\*.sdb\")]\n", + "query": "sequence by process.entity_id with maxspan = 5m\n [process where event.type in (\"start\", \"process_started\") and\n not (process.name : \"sdbinst.exe\" and process.parent.name : \"msiexec.exe\")]\n [registry where event.type in (\"creation\", \"change\") and\n registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\\\\Custom\\\\*.sdb\"]\n", "risk_score": 21, "rule_id": "c5ce48a6-7f57-4ee8-9313-3d0024caee10", "severity": "medium", @@ -47,5 +48,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json index 1e8f5b339ba608..393e593bed4825 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Detects attempts to maintain persistence by creating registry keys using AppCert DLLs. AppCert DLLs are loaded by every process using the common API functions to create processes.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appinitdlls_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appinitdlls_registry.json index b5be845a50e66e..21310499ecbd7e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appinitdlls_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appinitdlls_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Attackers may maintain persistence by creating registry keys using AppInit DLLs. AppInit DLLs are loaded by every process using the common library, user32.dll.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_hidden_login_item_osascript.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_hidden_login_item_osascript.json new file mode 100644 index 00000000000000..e105b91362adb1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_hidden_login_item_osascript.json @@ -0,0 +1,75 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of osascript to create a hidden login item. This may indicate an attempt to persist a malicious program while concealing its presence.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Creation of Hidden Login Item via Apple Script", + "query": "process where event.type in (\"start\", \"process_started\") and process.name : \"osascript\" and\n process.command_line : \"osascript*login item*hidden:true*\"\n", + "risk_score": 47, + "rule_id": "f24bcae1-8980-4b30-b5dd-f851b055c9e7", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.011", + "name": "Plist Modification", + "reference": "https://attack.mitre.org/techniques/T1547/011/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/", + "subtechnique": [ + { + "id": "T1059.002", + "name": "AppleScript", + "reference": "https://attack.mitre.org/techniques/T1059/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json new file mode 100644 index 00000000000000..1445d9c489dba0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Authorization plugins are used to extend the authorization services API and implement mechanisms that are not natively supported by the OS, such as multi-factor authentication with third party software. Adversaries may abuse this feature to persist and/or collect clear text credentials as they traverse the registered plugins during user logon.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Authorization Plugin Modification", + "query": "event.category:file and not event.type:deletion and file.path:(/Library/Security/SecurityAgentPlugins/* and not /Library/Security/SecurityAgentPlugins/TeamViewerAuthPlugin.bundle/Contents/*)", + "references": [ + "https://developer.apple.com/documentation/security/authorization_plug-ins", + "https://www.xorrior.com/persistent-credential-theft/" + ], + "risk_score": 47, + "rule_id": "e6c98d38-633d-4b3e-9387-42112cd5ac10", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.002", + "name": "Authentication Package", + "reference": "https://attack.mitre.org/techniques/T1547/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json new file mode 100644 index 00000000000000..2d250ede2832b8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json @@ -0,0 +1,71 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may modify the standard authentication module for persistence via patching the normal authorization process or modifying the login configuration to allow unauthorized access or elevate privileges.", + "false_positives": [ + "Trusted system module updates or allowed Pluggable Authentication Module (PAM) daemon configuration changes." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Modification of Standard Authentication Module or Configuration", + "query": "event.category:file and event.type:change and (file.name:pam_*.so or file.path:(/etc/pam.d/* or /private/etc/pam.d/*)) and process.executable: (* and not ( /bin/yum or \"/usr/sbin/pam-auth-update\" or /usr/libexec/packagekitd or /usr/bin/dpkg or /usr/bin/vim or /usr/libexec/xpcproxy or /usr/bin/bsdtar or /usr/local/bin/brew ) )", + "references": [ + "https://github.com/zephrax/linux-pam-backdoor", + "https://github.com/eurialo/pambd", + "http://0x90909090.blogspot.com/2016/06/creating-backdoor-in-pam-in-5-line-of.html", + "https://www.trendmicro.com/en_us/research/19/i/skidmap-linux-malware-uses-rootkit-capabilities-to-hide-cryptocurrency-mining-payload.html" + ], + "risk_score": 47, + "rule_id": "93f47b6f-5728-4004-ba00-625083b3dcb0", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Linux", + "Threat Detection", + "Credential Access", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1556", + "name": "Modify Authentication Process", + "reference": "https://attack.mitre.org/techniques/T1556/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json new file mode 100644 index 00000000000000..8b9df12761d20c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json @@ -0,0 +1,67 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may modify SSH related binaries for persistence or credential access by patching sensitive functions to enable unauthorized access or by logging SSH credentials for exfiltration.", + "false_positives": [ + "Trusted OpenSSH executable updates. It's recommended to verify the integrity of OpenSSH binary changes." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Modification of OpenSSH Binaries", + "query": "event.category:file and event.type:change and process.name:* and (file.path:(/usr/sbin/sshd or /usr/bin/ssh or /usr/bin/sftp or /usr/bin/scp) or file.name:libkeyutils.so) and not process.executable:/usr/bin/dpkg", + "references": [ + "https://blog.angelalonso.es/2016/09/anatomy-of-real-linux-intrusion-part-ii.html" + ], + "risk_score": 47, + "rule_id": "0415f22a-2336-45fa-ba07-618a5942e22c", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Credential Access", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1556", + "name": "Modify Authentication Process", + "reference": "https://attack.mitre.org/techniques/T1556/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_cron_jobs_creation_and_runtime.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_cron_jobs_creation_and_runtime.json new file mode 100644 index 00000000000000..7969feb7e204c9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_cron_jobs_creation_and_runtime.json @@ -0,0 +1,60 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or execution of a cron job. Adversaries may abuse cron jobs to perform task scheduling for initial or recurring execution of malicious code.", + "false_positives": [ + "Legitimate software or scripts using cron jobs for recurring tasks." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Persistence via Cron Job", + "query": "event.category:process and event.type:(start or process_started or info) and not user.name:root and ((process.name:crontab and not process.args:(\"-l\" or \"-r\" or \"-e\" or \"-help\" or \"-h\")) or (process.parent.name:cron and not process.name:\"running job\" and not process.executable:(/Applications/Docker.app/Contents/Resources/bin/docker or /usr/bin/killall or /usr/sbin/sendmail or /usr/bin/env or /usr/bin/timeshift or /bin/rm)))", + "references": [ + "https://archive.f-secure.com/weblog/archives/00002576.html", + "https://ss64.com/osx/crontab.html" + ], + "risk_score": 21, + "rule_id": "b1c14366-f4f8-49a0-bcbb-51d2de8b0bb8", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1053", + "name": "Scheduled Task/Job", + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.003", + "name": "Cron", + "reference": "https://attack.mitre.org/techniques/T1053/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json new file mode 100644 index 00000000000000..1c2628871b8d08 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json @@ -0,0 +1,80 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a launchd child process with a hidden file. An adversary can establish persistence by installing a new logon item, launch agent, or daemon that executes upon login.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Suspicious Hidden Child Process of Launchd", + "query": "event.category:process and event.type:(start or process_started) and process.name:.* and process.parent.executable:/sbin/launchd", + "references": [ + "https://objective-see.com/blog/blog_0x61.html", + "https://www.intezer.com/blog/research/operation-electrorat-attacker-creates-fake-companies-to-drain-your-crypto-wallets/", + "https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html" + ], + "risk_score": 47, + "rule_id": "083fa162-e790-4d85-9aeb-4fea04188adb", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/", + "subtechnique": [ + { + "id": "T1543.001", + "name": "Launch Agent", + "reference": "https://attack.mitre.org/techniques/T1543/001/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1564", + "name": "Hide Artifacts", + "reference": "https://attack.mitre.org/techniques/T1564/", + "subtechnique": [ + { + "id": "T1564.001", + "name": "Hidden Files and Directories", + "reference": "https://attack.mitre.org/techniques/T1564/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json new file mode 100644 index 00000000000000..8fe0648e8e5a98 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json @@ -0,0 +1,48 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of a DirectoryService PlugIns (dsplug) file. The DirectoryService daemonlaunches on each system boot and automatically reloads after crash. It scans and executes bundles that are located in the DirectoryServices PlugIns folder and can be abused by adversaries to maintain persistence.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Persistence via DirectoryService Plugin Modification", + "query": "event.category:file and not event.type:deletion and file.path:/Library/DirectoryServices/PlugIns/*.dsplug", + "references": [ + "https://blog.chichou.me/2019/11/21/two-macos-persistence-tricks-abusing-plugins/" + ], + "risk_score": 47, + "rule_id": "89fa6cb7-6b53-4de2-b604-648488841ab8", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json new file mode 100644 index 00000000000000..096ebc04ddeb5c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json @@ -0,0 +1,48 @@ +{ + "author": [ + "Elastic" + ], + "description": "An adversary can establish persistence by modifying an existing macOS dock property list in order to execute a malicious application instead of the intended one when invoked.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Persistence via Docker Shortcut Modification", + "query": "event.category : file and event.action : modification and file.path : /Users/*/Library/Preferences/com.apple.dock.plist and not process.name : (xpcproxy or cfprefsd or plutil or jamf or PlistBuddy or InstallerRemotePluginService)", + "references": [ + "https://github.com/specterops/presentations/raw/master/Leo Pitt/Hey_Im_Still_in_Here_Modern_macOS_Persistence_SO-CON2020.pdf" + ], + "risk_score": 47, + "rule_id": "c81cefcb-82b9-4408-a533-3c3df549e62d", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json new file mode 100644 index 00000000000000..71375e835cf414 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of the Event Monitor Daemon (emond) rules. Adversaries may abuse this service by writing a rule to execute commands when a defined event occurs, such as system start up or user authentication.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Emond Rules Creation or Modification", + "query": "file where event.type != \"deletion\" and\n file.path : (\"/private/etc/emond.d/rules/*.plist\", \"/etc/emon.d/rules/*.plist\")\n", + "references": [ + "https://www.xorrior.com/emond-persistence/" + ], + "risk_score": 47, + "rule_id": "a6bf4dd4-743e-4da8-8c03-3ebd753a6c90", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1546", + "name": "Event Triggered Execution", + "reference": "https://attack.mitre.org/techniques/T1546/", + "subtechnique": [ + { + "id": "T1546.014", + "name": "Emond", + "reference": "https://attack.mitre.org/techniques/T1546/014/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_process_execution.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_process_execution.json new file mode 100644 index 00000000000000..62e93786786b48 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_process_execution.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a suspicious child process of the Event Monitor Daemon (emond). Adversaries may abuse this service by writing a rule to execute commands when a defined event occurs, such as system start up or user authentication.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious Emond Child Process", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"emond\" and\n process.name : (\n \"bash\",\n \"dash\",\n \"sh\",\n \"tcsh\",\n \"csh\",\n \"zsh\",\n \"ksh\",\n \"fish\",\n \"Python\",\n \"python*\",\n \"perl*\",\n \"php*\",\n \"osascript\",\n \"pwsh\",\n \"curl\",\n \"wget\",\n \"cp\",\n \"mv\",\n \"touch\",\n \"echo\",\n \"base64\",\n \"launchctl\")\n", + "references": [ + "https://www.xorrior.com/emond-persistence/" + ], + "risk_score": 47, + "rule_id": "3e3d15c6-1509-479a-b125-21718372157e", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Execution", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1546", + "name": "Event Triggered Execution", + "reference": "https://attack.mitre.org/techniques/T1546/", + "subtechnique": [ + { + "id": "T1546.014", + "name": "Emond", + "reference": "https://attack.mitre.org/techniques/T1546/014/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json new file mode 100644 index 00000000000000..e7c27e8d33caf3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to enable the root account using the dsenableroot command. This command may be abused by adversaries for persistence, as the root account is disabled by default.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Attempt to Enable the Root Account", + "query": "event.category:process and event.type:(start or process_started) and process.name:dsenableroot and not process.args:\"-d\"", + "references": [ + "https://ss64.com/osx/dsenableroot.html" + ], + "risk_score": 47, + "rule_id": "cc2fd2d0-ba3a-4939-b87f-2901764ed036", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/", + "subtechnique": [ + { + "id": "T1078.003", + "name": "Local Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_launch_agent_deamon_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_launch_agent_deamon_creation.json new file mode 100644 index 00000000000000..4129a18994f112 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_launch_agent_deamon_creation.json @@ -0,0 +1,78 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation of a hidden launch agent or daemon. An adversary may establish persistence by installing a new launch agent or daemon which executes at login.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Creation of Hidden Launch Agent or Daemon", + "query": "file where event.type != \"deletion\" and\n file.path : \n (\n \"/System/Library/LaunchAgents/.*.plist\",\n \"/Library/LaunchAgents/.*.plist\",\n \"/Users/*/Library/LaunchAgents/.*.plist\",\n \"/System/Library/LaunchDaemons/.*.plist\",\n \"/Library/LaunchDaemons/.*.plist\"\n )\n", + "references": [ + "https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html" + ], + "risk_score": 47, + "rule_id": "092b068f-84ac-485d-8a55-7dd9e006715f", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/", + "subtechnique": [ + { + "id": "T1543.001", + "name": "Launch Agent", + "reference": "https://attack.mitre.org/techniques/T1543/001/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1564", + "name": "Hide Artifacts", + "reference": "https://attack.mitre.org/techniques/T1564/", + "subtechnique": [ + { + "id": "T1564.001", + "name": "Hidden Files and Directories", + "reference": "https://attack.mitre.org/techniques/T1564/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json new file mode 100644 index 00000000000000..bfe3c1f20cb812 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json @@ -0,0 +1,51 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation of a hidden local user account by appending the dollar sign to the account name. This is sometimes done by attackers to increase access to a system and avoid appearing in the results of accounts listing using the net users command.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Creation of a Hidden Local User Account", + "query": "registry where registry.path : \"HKLM\\\\SAM\\\\SAM\\\\Domains\\\\Account\\\\Users\\\\Names\\\\*$\\\\\"\n", + "references": [ + "https://blog.menasec.net/2019/02/threat-hunting-6-hiding-in-plain-sights_8.html", + "https://github.com/CyberMonitor/APT_CyberCriminal_Campagin_Collections/tree/master/2020/2020.12.15.Lazarus_Campaign" + ], + "risk_score": 73, + "rule_id": "2edc8076-291e-41e9-81e4-e3fcbc97ae5e", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1136", + "name": "Create Account", + "reference": "https://attack.mitre.org/techniques/T1136/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json index 03cb315a9982ff..9e83f609ac830a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "The Debugger and SilentProcessExit registry keys can allow an adversary to intercept the execution of files, causing a different process to be executed. This functionality can be abused by an adversary to establish persistence.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -50,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_finder_sync_plugin_pluginkit.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_finder_sync_plugin_pluginkit.json new file mode 100644 index 00000000000000..0fa1058918a194 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_finder_sync_plugin_pluginkit.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Finder Sync plugins enable users to extend Finder\u2019s functionality by modifying the user interface. Adversaries may abuse this feature by adding a rogue Finder Plugin to repeatedly execute malicious payloads for persistence.", + "false_positives": [ + "Trusted Finder Sync Plugins" + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Finder Sync Plugin Registered and Enabled", + "query": "sequence by host.id, user.id with maxspan = 5s\n [process where event.type in (\"start\", \"process_started\") and process.name : \"pluginkit\" and process.args : \"-a\"]\n [process where event.type in (\"start\", \"process_started\") and process.name : \"pluginkit\" and\n process.args : \"-e\" and process.args : \"use\" and process.args : \"-i\" and\n not process.args :\n (\n \"com.google.GoogleDrive.FinderSyncAPIExtension\",\n \"com.google.drivefs.findersync\",\n \"com.boxcryptor.osx.Rednif\",\n \"com.adobe.accmac.ACCFinderSync\",\n \"com.microsoft.OneDrive.FinderSync\",\n \"com.insynchq.Insync.Insync-Finder-Integration\",\n \"com.box.desktop.findersyncext\"\n )\n ]\n", + "references": [ + "https://github.com/specterops/presentations/raw/master/Leo Pitt/Hey_Im_Still_in_Here_Modern_macOS_Persistence_SO-CON2020.pdf" + ], + "risk_score": 47, + "rule_id": "37f638ea-909d-4f94-9248-edd21e4a9906", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/" + } + ] + } + ], + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json index 17d2505f4aaca3..c3eb9584b7dfb2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kde_autostart_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kde_autostart_modification.json new file mode 100644 index 00000000000000..2ecb3624e4fcaf --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kde_autostart_modification.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of a K Desktop Environment (KDE) AutoStart script or desktop file that will execute upon each user logon. Adversaries may abuse this method for persistence.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Persistence via KDE AutoStart Script or Desktop File Modification", + "query": "file where event.type != \"deletion\" and\n file.extension in (\"sh\", \"desktop\") and\n file.path :\n (\n \"/home/*/.config/autostart/*\", \"/root/.config/autostart/*\",\n \"/home/*/.kde/Autostart/*\", \"/root/.kde/Autostart/*\",\n \"/home/*/.kde4/Autostart/*\", \"/root/.kde4/Autostart/*\",\n \"/home/*/.kde/share/autostart/*\", \"/root/.kde/share/autostart/*\",\n \"/home/*/.kde4/share/autostart/*\", \"/root/.kde4/share/autostart/*\",\n \"/home/*/.local/share/autostart/*\", \"/root/.local/share/autostart/*\",\n \"/home/*/.config/autostart-scripts/*\", \"/root/.config/autostart-scripts/*\",\n \"/etc/xdg/autostart/*\", \"/usr/share/autostart/*\"\n )\n", + "references": [ + "https://userbase.kde.org/System_Settings/Autostart", + "https://www.amnesty.org/en/latest/research/2020/09/german-made-finspy-spyware-found-in-egypt-and-mac-and-linux-versions-revealed/", + "https://www.intezer.com/blog/research/operation-electrorat-attacker-creates-fake-companies-to-drain-your-crypto-wallets/" + ], + "risk_score": 47, + "rule_id": "e3e904b3-0a8e-4e68-86a8-977a163e21d3", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json index 3f1216c2fcc33d..da5967d6d45965 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_commands.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json index ad28885de07404..69e2eee696cb39 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json @@ -9,14 +9,15 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Scheduled Task Created by a Windows Script", "note": "Decode the base64 encoded Tasks Actions registry value to investigate the task's configured action.", - "query": "sequence by host.id with maxspan = 30s\n [library where file.name : \"taskschd.dll\" and process.name : (\"cscript.exe\", \"wscript.exe\", \"powershell.exe\")]\n [registry where registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\TaskCache\\\\Tasks\\\\*\\\\Actions\"]\n", - "risk_score": 43, + "query": "sequence by host.id with maxspan = 30s\n [library where dll.name : \"taskschd.dll\" and process.name : (\"cscript.exe\", \"wscript.exe\", \"powershell.exe\")]\n [registry where registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\TaskCache\\\\Tasks\\\\*\\\\Actions\"]\n", + "risk_score": 47, "rule_id": "689b9d57-e4d5-4357-ad17-9c334609d79a", "severity": "medium", "tags": [ @@ -44,5 +45,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json new file mode 100644 index 00000000000000..22d82b567f362e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of the login window property list (plist). Adversaries may modify plist files to run a program during system boot or user login for persistence.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Persistence via Login Hook", + "note": "Starting in Mac OS X 10.7 (Lion), users can specify certain applications to be re-opened when a user reboots their machine. This can be abused to establish or maintain persistence on a compromised system.", + "query": "event.category:\"file\" and not event.type:\"deletion\" and file.name:\"com.apple.loginwindow.plist\" and process.name:(* and not (systemmigrationd or DesktopServicesHelper or diskmanagementd or rsync or launchd or cfprefsd or xpcproxy or ManagedClient or MCXCompositor))", + "references": [ + "https://github.com/D00MFist/PersistentJXA/blob/master/LoginScript.js" + ], + "risk_score": 47, + "rule_id": "ac412404-57a5-476f-858f-4e8fbb4f48d8", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.011", + "name": "Plist Modification", + "reference": "https://attack.mitre.org/techniques/T1547/011/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_modification_sublime_app_plugin_or_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_modification_sublime_app_plugin_or_script.json new file mode 100644 index 00000000000000..64d2b7834c95a9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_modification_sublime_app_plugin_or_script.json @@ -0,0 +1,48 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries may create or modify the Sublime application plugins or scripts to execute a malicious payload each time the Sublime application is started.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Sublime Plugin or Application Script Modification", + "query": "file where event.type in (\"change\", \"creation\") and file.extension : \"py\" and\n file.path : \n (\n \"/Users/*/Library/Application Support/Sublime Text*/Packages/*.py\", \n \"/Applications/Sublime Text.app/Contents/MacOS/sublime.py\"\n ) and\n not process.executable : \n (\n \"/Applications/Sublime Text*.app/Contents/MacOS/Sublime Text*\", \n \"/usr/local/Cellar/git/*/bin/git\", \n \"/usr/libexec/xpcproxy\", \n \"/System/Library/PrivateFrameworks/DesktopServicesPriv.framework/Versions/A/Resources/DesktopServicesHelper\", \n \"/Applications/Sublime Text.app/Contents/MacOS/plugin_host\"\n )\n", + "references": [ + "https://posts.specterops.io/persistent-jxa-66e1c3cd1cf5" + ], + "risk_score": 21, + "rule_id": "88817a33-60d3-411f-ba79-7c905d865b2a", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1554", + "name": "Compromise Client Software Binary", + "reference": "https://attack.mitre.org/techniques/T1554/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_office_addins_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_office_addins_file.json index 573ff7f7b43310..7da9b515b64579 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_office_addins_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_office_addins_file.json @@ -6,16 +6,17 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Persistence via Microsoft Office AddIns", - "query": "file where event.type != \"deletion\" and\n wildcard(file.extension,\"wll\",\"xll\",\"ppa\",\"ppam\",\"xla\",\"xlam\") and\n wildcard(file.path, \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Word\\\\Startup\\\\*\",\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\AddIns\\\\*\",\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Excel\\\\XLSTART\\\\*\")\n", + "query": "file where event.type != \"deletion\" and\n file.extension : (\"wll\",\"xll\",\"ppa\",\"ppam\",\"xla\",\"xlam\") and\n file.path :\n (\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Word\\\\Startup\\\\*\",\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\AddIns\\\\*\",\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Excel\\\\XLSTART\\\\*\"\n )\n", "references": [ "https://labs.mwrinfosecurity.com/blog/add-in-opportunities-for-office-persistence/" ], - "risk_score": 71, + "risk_score": 73, "rule_id": "f44fa4b6-524c-4e87-8d9e-a32599e4fb7c", "severity": "high", "tags": [ @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_outlook_vba_template.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_outlook_vba_template.json index 9192ea9ab3961d..1c1eddeb91a9ed 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_outlook_vba_template.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_outlook_vba_template.json @@ -9,17 +9,18 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Persistence via Microsoft Outlook VBA", - "query": "file where event.type != \"deletion\" and\n wildcard(file.path, \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Outlook\\\\VbaProject.OTM\")\n", + "query": "file where event.type != \"deletion\" and\n file.path : \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Outlook\\\\VbaProject.OTM\"\n", "references": [ "https://www.mdsec.co.uk/2020/11/a-fresh-outlook-on-mail-based-persistence/", "https://www.linkedin.com/pulse/outlook-backdoor-using-vba-samir-b-/" ], - "risk_score": 43, + "risk_score": 47, "rule_id": "397945f3-d39a-4e6f-8bcb-9656c2031438", "severity": "medium", "tags": [ @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json new file mode 100644 index 00000000000000..e54b368a24cc26 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation or modification of the default configuration for periodic tasks. Adversaries may abuse periodic tasks to execute malicious code or maintain persistence.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Persistence via Periodic Tasks", + "query": "event.category:\"file\" and not event.type:\"deletion\" and file.path:(/private/etc/periodic/* or /private/etc/defaults/periodic.conf or /private/etc/periodic.conf)", + "references": [ + "https://opensource.apple.com/source/crontabs/crontabs-13/private/etc/defaults/periodic.conf.auto.html", + "https://www.oreilly.com/library/view/mac-os-x/0596003706/re328.html", + "https://github.com/D00MFist/PersistentJXA/blob/master/PeriodicPersist.js" + ], + "risk_score": 21, + "rule_id": "48ec9452-e1fd-4513-a376-10a1a26d2c83", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1053", + "name": "Scheduled Task/Job", + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.003", + "name": "Cron", + "reference": "https://attack.mitre.org/techniques/T1053/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json index 1cd66ea45dea36..69d84e6082f7ea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Windows contains accessibility features that may be launched with a key combination before a user has logged in. An adversary can modify the way these programs are launched to get a command prompt or backdoor without logging in to the system.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -72,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json index 93e67ccab04b2d..52ca728d933f59 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Detects changes to registry persistence keys that are uncommonly used or modified by legitimate programs. This could be an indication of an adversary's attempt to persist in a stealthy manner.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -52,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json index 62ca418bbfdecd..2e06beaa4e32b3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies run key or startup key registry modifications. In order to survive reboots and other system interrupts, attackers will modify run keys within the registry or leverage startup folder items as a form of persistence.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json index 52d0720839f5cd..c4c1c1f23b6a58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies execution of suspicious persistent programs (scripts, rundll32, etc.) by looking at process lineage and command line usage.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -46,5 +48,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json index 8d90717ec69fc0..f95d6e883adf2d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies processes modifying the services registry key directly, instead of through the expected Windows APIs. This could be an indication of an adversary attempting to stealthily persist through abnormal service creation or modification of an existing service.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json new file mode 100644 index 00000000000000..78fa1e65d1e0c3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json @@ -0,0 +1,60 @@ +{ + "author": [ + "Elastic" + ], + "description": "Both ~/.bash_profile and ~/.bashrc are files containing shell commands that are run when Bash is invoked. These files are executed in a user's context, either interactively or non-interactively, when a user logs in so that their environment is set correctly. Adversaries may abuse this to establish persistence by executing malicious content triggered by a user\u2019s shell.", + "false_positives": [ + "Changes to the Shell Profile tend to be noisy, a tuning per your environment will be required." + ], + "from": "now-9m", + "index": [ + "logs-endpoint.events.*", + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Bash Shell Profile Modification", + "query": "event.category:file and event.type:change and process.name:(* and not (sudo or vim or zsh or env or nano or bash or Terminal or xpcproxy or login or cat or cp or launchctl or java)) and not process.executable:(/Applications/* or /private/var/folders/* or /usr/local/*) and file.path:(/private/etc/rc.local or /etc/rc.local or /home/*/.profile or /home/*/.profile1 or /home/*/.bash_profile or /home/*/.bash_profile1 or /home/*/.bashrc or /Users/*/.bash_profile or /Users/*/.zshenv)", + "references": [ + "https://www.anomali.com/blog/pulling-linux-rabbit-rabbot-malware-out-of-a-hat" + ], + "risk_score": 47, + "rule_id": "e6c1a552-7776-44ad-ae0f-8746cc07773c", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Linux", + "Threat Detection", + "Execution", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1546", + "name": "Event Triggered Execution", + "reference": "https://attack.mitre.org/techniques/T1546/", + "subtechnique": [ + { + "id": "T1546.004", + "name": ".bash_profile and .bashrc", + "reference": "https://attack.mitre.org/techniques/T1546/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json new file mode 100644 index 00000000000000..aa24a832e594ca --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "The Secure Shell (SSH) authorized_keys file specifies which users are allowed to log into a server using public key authentication. Adversaries may modify it to maintain persistence on a victim host by adding their own public key(s).", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "SSH Authorized Keys File Modification", + "query": "event.category:file and event.type:(change or creation) and file.name:(\"authorized_keys\" or \"authorized_keys2\") and not process.executable: (/Library/Developer/CommandLineTools/usr/bin/git or /usr/local/Cellar/maven/*/libexec/bin/mvn or /Library/Java/JavaVirtualMachines/jdk*.jdk/Contents/Home/bin/java or /usr/bin/vim or /usr/local/Cellar/coreutils/*/bin/gcat or /usr/bin/bsdtar or /usr/bin/nautilus or /usr/bin/scp or /usr/bin/touch or /var/lib/docker/*)", + "risk_score": 47, + "rule_id": "2215b8bd-1759-4ffa-8ab8-55c8e6b32e7f", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1098", + "name": "Account Manipulation", + "reference": "https://attack.mitre.org/techniques/T1098/", + "subtechnique": [ + { + "id": "T1098.004", + "name": "SSH Authorized Keys", + "reference": "https://attack.mitre.org/techniques/T1098/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json index 5defde988ac3b4..c202a416c3202e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies files written to or modified in the startup folder by commonly abused processes. Adversaries may use this technique to maintain persistence.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json index 67c9c3db6ba2aa..bf58c0f084baa4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json @@ -3,6 +3,7 @@ "Elastic" ], "description": "Identifies files written or modified in the startup folder by unsigned processes. Adversaries may abuse this technique to maintain persistence in an environment.", + "from": "now-9m", "index": [ "logs-endpoint.events.*" ], @@ -45,5 +46,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json index f689796a3673ed..6ea42440c3f91c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies script engines creating files in the startup folder, or the creation of script files in the startup folder.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json new file mode 100644 index 00000000000000..e454dac4dba99c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies suspicious modifications of the calendar file by an unusual process. Adversaries may create a custom calendar notification procedure to execute a malicious program at a recurring interval to establish persistence.", + "false_positives": [ + "Trusted applications for managing calendars and reminders." + ], + "from": "now-9m", + "index": [ + "logs-endpoint.events.*", + "auditbeat-*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Suspicious Calendar File Modification", + "query": "event.category:file and event.action:modification and file.path:/Users/*/Library/Calendars/*.calendar/Events/*.ics and process.executable: (* and not ( /System/Library/* or /System/Applications/Calendar.app/Contents/MacOS/* or /usr/libexec/xpcproxy or /sbin/launchd or /Applications/* ) )", + "references": [ + "https://labs.f-secure.com/blog/operationalising-calendar-alerts-persistence-on-macos", + "https://github.com/FSecureLABS/CalendarPersist", + "https://github.com/D00MFist/PersistentJXA/blob/master/CalendarPersist.js" + ], + "risk_score": 47, + "rule_id": "cb71aa62-55c8-42f0-b0dd-afb0bb0b1f51", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1546", + "name": "Event Triggered Execution", + "reference": "https://attack.mitre.org/techniques/T1546/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json index 41e8bd04b87ef3..7a7f0906b65b9b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies Component Object Model (COM) hijacking via registry modification. Adversaries may establish persistence by executing malicious content triggered by hijacked references to COM objects.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -50,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json index 77e63a546a896c..856ed7127aa9d4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json @@ -3,14 +3,16 @@ "Elastic" ], "description": "Identifies a suspicious image load (taskschd.dll) from Microsoft Office processes. This behavior may indicate adversarial activity where a scheduled task is configured via Windows Component Object Model (COM). This technique can be used to configure persistence and evade monitoring by avoiding the usage of the traditional Windows binary (schtasks.exe) used to manage scheduled tasks.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious Image Load (taskschd.dll) from MS Office", - "query": "library where process.name in (\"WINWORD.EXE\", \"EXCEL.EXE\", \"POWERPNT.EXE\", \"MSPUB.EXE\", \"MSACCESS.EXE\") and\n event.action == \"load\" and\n event.category == \"library\" and\n file.name == \"taskschd.dll\"\n", + "query": "library where process.name : (\"WINWORD.EXE\", \"EXCEL.EXE\", \"POWERPNT.EXE\", \"MSPUB.EXE\", \"MSACCESS.EXE\") and\n event.action : \"load\" and\n event.category : \"library\" and\n dll.name : \"taskschd.dll\"\n", "references": [ "https://medium.com/threatpunter/detecting-adversary-tradecraft-with-image-load-event-logging-and-eql-8de93338c16", "https://www.clearskysec.com/wp-content/uploads/2020/10/Operation-Quicksand.pdf" @@ -44,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json index 51d19bdaef6dbd..0ccec7f29805b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json @@ -9,13 +9,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "Suspicious Execution via Scheduled Task", "query": "process where event.type == \"start\" and\n /* Schedule service cmdline on Win10+ */\n process.parent.name : \"svchost.exe\" and process.parent.args : \"Schedule\" and\n /* add suspicious programs here */\n process.pe.original_file_name in\n (\n \"cscript.exe\",\n \"wscript.exe\",\n \"PowerShell.EXE\",\n \"Cmd.Exe\",\n \"MSHTA.EXE\",\n \"RUNDLL32.EXE\",\n \"REGSVR32.EXE\",\n \"MSBuild.exe\",\n \"InstallUtil.exe\",\n \"RegAsm.exe\",\n \"RegSvcs.exe\",\n \"msxsl.exe\",\n \"CONTROL.EXE\",\n \"EXPLORER.EXE\",\n \"Microsoft.Workflow.Compiler.exe\",\n \"msiexec.exe\"\n ) and\n /* add suspicious paths here */\n process.args : (\n \"C:\\\\Users\\\\*\",\n \"C:\\\\ProgramData\\\\*\", \n \"C:\\\\Windows\\\\Temp\\\\*\", \n \"C:\\\\Windows\\\\Tasks\\\\*\", \n \"C:\\\\PerfLogs\\\\*\", \n \"C:\\\\Intel\\\\*\", \n \"C:\\\\Windows\\\\Debug\\\\*\", \n \"C:\\\\HP\\\\*\")\n", - "risk_score": 43, + "risk_score": 47, "rule_id": "5d1d6907-0747-4d5d-9b24-e4a18853dc0a", "severity": "medium", "tags": [ @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json index 487327f6344bad..a9cba3e9be599b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies the creation of a suspicious ImagePath value. This could be an indication of an adversary attempting to stealthily persist or escalate privileges through abnormal service creation.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json index 33198e716af0de..80a0e067aa26f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json new file mode 100644 index 00000000000000..72310046bf35c3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Windows operating systems are utilizing the time provider architecture in order to obtain accurate time stamps from other network devices or clients in the network. Time providers are implemented in the form of a DLL file which resides in System32 folder. The service W32Time initiates during the startup of Windows and loads w32time.dll. Adversaries may abuse this architecture to establish persistence, specifically by registering and enabling a malicious DLL as a time provider.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential Persistence via Time Provider Modification", + "query": "registry where event.type:\"change\" and\n registry.path:\"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\W32Time\\\\TimeProviders\\\\*\" and\n registry.data.strings:\"*.dll\"\n", + "references": [ + "https://pentestlab.blog/2019/10/22/persistence-time-providers/" + ], + "risk_score": 47, + "rule_id": "14ed1aa9-ebfd-4cf9-a463-0ac59ec55204", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.003", + "name": "Time Providers", + "reference": "https://attack.mitre.org/techniques/T1547/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json new file mode 100644 index 00000000000000..7891df2ca55882 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic", + "Skoetting" + ], + "description": "Identifies a user being added to a privileged group in Active Directory. Privileged accounts and groups in Active Directory are those to which powerful rights, privileges, and permissions are granted that allow them to perform nearly any action in Active Directory and on domain-joined systems.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "User Added to Privileged Group in Active Directory", + "query": "event.category:iam and event.action:\"added-member-to-group\" and group.name:(Administrators or \"Local Administrators\" or \"Domain Admins\" or \"Enterprise Admins\" or \"Backup Admins\" or \"Schema Admins\" or \"DnsAdmins\")", + "references": [ + "https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-b--privileged-accounts-and-groups-in-active-directory" + ], + "risk_score": 21, + "rule_id": "5cd8e1f7-0050-4afc-b2df-904e40b2f5ae", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1136", + "name": "Create Account", + "reference": "https://attack.mitre.org/techniques/T1136/", + "subtechnique": [ + { + "id": "T1136.001", + "name": "Local Account", + "reference": "https://attack.mitre.org/techniques/T1136/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json index cbc8ea15bb800f..cb9c70a842b2df 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -41,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_application_shimming.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_application_shimming.json index a9ca3e2c8da488..f910ae13d92cdd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_application_shimming.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_application_shimming.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "The Application Shim was created to allow for backward compatibility of software as the operating system codebase changes over time. This Windows functionality has been abused by attackers to stealthily gain persistence and arbitrary code execution in legitimate Windows processes.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -69,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json new file mode 100644 index 00000000000000..c2ae7b2ff335d8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json @@ -0,0 +1,32 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modifications to the Atom desktop text editor Init File. Adversaries may add malicious JavaScript code to the init.coffee file that will be executed upon the Atom application opening.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Persistence via Atom Init Script Modification", + "query": "event.category:\"file\" and not event.type:\"deletion\" and file.path:/Users/*/.atom/init.coffee and not process.name:(Atom or xpcproxy) and not user.name:root", + "references": [ + "https://github.com/D00MFist/PersistentJXA/blob/master/AtomPersist.js", + "https://flight-manual.atom.io/hacking-atom/sections/the-init-file/" + ], + "risk_score": 21, + "rule_id": "b4449455-f986-4b5a-82ed-e36b129331f7", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json index 23b316fad1db5d..3fe0137ec8efb0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -52,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json index ea861b2634d837..983c33ca288ac4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies registry modifications related to the Windows Security Support Provider (SSP) configuration. Adversaries may abuse this to establish persistence in an environment.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -47,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json index 71114206fb47df..4543def3253ad8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json index 1ed0077a66529f..bcfd0e6b94ac6e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], - "language": "kuery", + "language": "eql", "license": "Elastic License", "name": "Persistence via Update Orchestrator Service Hijack", - "query": "event.category:process and event.type:(start or process_started) and process.parent.name:svchost.exe and process.parent.args:(UsoSvc or usosvc) and not process.name:(UsoClient.exe or usoclient.exe or MusNotification.exe or musnotification.exe or MusNotificationUx.exe or musnotificationux.exe)", + "query": "process where event.type == \"start\" and\n process.parent.executable : \"C:\\\\Windows\\\\System32\\\\svchost.exe\" and\n process.parent.args : \"UsoSvc\" and\n not process.executable :\n (\n \"C:\\\\Windows\\\\System32\\\\UsoClient.exe\",\n \"C:\\\\Windows\\\\System32\\\\MusNotification.exe\",\n \"C:\\\\Windows\\\\System32\\\\MusNotificationUx.exe\",\n \"C:\\\\Windows\\\\System32\\\\MusNotifyIcon.exe\",\n \"C:\\\\Windows\\\\System32\\\\WerFault.exe\",\n \"C:\\\\Windows\\\\System32\\\\WerMgr.exe\"\n )\n", "references": [ "https://github.com/irsl/CVE-2020-1313" ], @@ -50,6 +51,6 @@ } ], "timestamp_override": "event.ingested", - "type": "query", - "version": 3 + "type": "eql", + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json index 17453925b3b3bf..d7bc2c41f95047 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "An adversary can use Windows Management Instrumentation (WMI) to install event filters, providers, consumers, and bindings that execute code when a defined event occurs. Adversaries may use the capabilities of WMI to subscribe to an event and execute arbitrary code when that event occurs, providing persistence on a system.", + "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -40,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_applescript_with_admin_privs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_applescript_with_admin_privs.json new file mode 100644 index 00000000000000..36faa1a5ed87c5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_applescript_with_admin_privs.json @@ -0,0 +1,64 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies execution of the Apple script interpreter (osascript) without a password prompt and with administrator privileges.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Apple Scripting Execution with Administrator Privileges", + "query": "process where event.type in (\"start\", \"process_started\") and process.name : \"osascript\" and\n process.command_line : \"osascript*with administrator privileges\"\n", + "references": [ + "https://discussions.apple.com/thread/2266150" + ], + "risk_score": 47, + "rule_id": "827f8d8f-4117-4ae4-b551-f56d54b9da6b", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Execution", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json new file mode 100644 index 00000000000000..b600fe5dc99508 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json @@ -0,0 +1,80 @@ +{ + "author": [ + "Elastic" + ], + "description": "User Account Control (UAC) can help mitigate the impact of malware on Windows hosts. With UAC, apps and tasks always run in the security context of a non-administrator account, unless an administrator specifically authorizes administrator-level access to the system. This rule identifies registry value changes to bypass User Access Control (UAC) protection.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Disabling User Account Control via Registry Modification", + "query": "registry where event.type == \"change\" and\n registry.path :\n (\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\EnableLUA\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\ConsentPromptBehaviorAdmin\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\PromptOnSecureDesktop\"\n ) and\n registry.data.strings : \"0\"\n", + "references": [ + "https://www.greyhathacker.net/?p=796", + "https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/user-account-control-group-policy-and-registry-key-settings", + "https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/user-account-control-overview" + ], + "risk_score": 47, + "rule_id": "d31f183a-e5b1-451b-8534-ba62bca0b404", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/", + "subtechnique": [ + { + "id": "T1548.002", + "name": "Bypass User Access Control", + "reference": "https://attack.mitre.org/techniques/T1548/002/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/", + "subtechnique": [ + { + "id": "T1548.002", + "name": "Bypass User Access Control", + "reference": "https://attack.mitre.org/techniques/T1548/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json new file mode 100644 index 00000000000000..b5cd27e6ff02b9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json @@ -0,0 +1,53 @@ +{ + "author": [ + "Elastic" + ], + "description": "A sudoers file specifies the commands users or groups can run and from which terminals. Adversaries can take advantage of these configurations to execute commands as other users or spawn processes with higher privileges.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Privilege Escalation via Sudoers File Modification", + "query": "event.category:process and event.type:start and process.args:(echo and *NOPASSWD*ALL*)", + "risk_score": 73, + "rule_id": "76152ca1-71d0-4003-9e37-0983e12832da", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/", + "subtechnique": [ + { + "id": "T1548.003", + "name": "Sudo and Sudo Caching", + "reference": "https://attack.mitre.org/techniques/T1548/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_apple_scripting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json similarity index 65% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_apple_scripting.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json index 1b741cd1a8c97c..ff735cbb40e42e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_apple_scripting.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json @@ -2,16 +2,16 @@ "author": [ "Elastic" ], - "description": "Identifies execution of the security_authtrampoline process via the Apple script interpreter (osascript). This occurs when programs use AuthorizationExecute-WithPrivileges from the Security.framework to run another program with root privileges. It should not be run by itself, as this is a sign of execution with explicit logon credentials.", + "description": "Identifies execution of the security_authtrampoline process via a scripting interpreter. This occurs when programs use AuthorizationExecute-WithPrivileges from the Security.framework to run another program with root privileges. It should not be run by itself, as this is a sign of execution with explicit logon credentials.", "from": "now-9m", "index": [ "auditbeat-*", "logs-endpoint.events.*" ], - "language": "eql", + "language": "kuery", "license": "Elastic License", - "name": "Execution with Explicit Credentials via Apple Scripting", - "query": "sequence by host.id with maxspan=5s\n [process where event.type in (\"start\", \"process_started\", \"info\") and process.name == \"osascript\"] by process.pid\n [process where event.type in (\"start\", \"process_started\") and process.name == \"security_authtrampoline\"] by process.ppid\n", + "name": "Execution with Explicit Credentials via Scripting", + "query": "event.category:process and event.type:(start or process_started) and process.name:\"security_authtrampoline\" and process.parent.name:(osascript or com.apple.automator.runner or sh or bash or dash or zsh or python* or perl* or php* or ruby or pwsh)", "references": [ "https://objectivebythesea.com/v2/talks/OBTS_v2_Thomas.pdf", "https://www.manpagez.com/man/8/security_authtrampoline/" @@ -59,6 +59,7 @@ ] } ], - "type": "eql", - "version": 1 + "timestamp_override": "event.ingested", + "type": "query", + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json new file mode 100644 index 00000000000000..f80e877bcb43c1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json @@ -0,0 +1,51 @@ +{ + "author": [ + "Elastic" + ], + "description": "Detects attempts to exploit privilege escalation vulnerabilities related to the Adobe Acrobat Reader PrivilegedHelperTool responsible for installing updates. For more information, refer to CVE-2020-9615, CVE-2020-9614 and CVE-2020-9613 and verify that the impacted system is patched.", + "false_positives": [ + "Trusted system or Adobe Acrobat Related processes." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Suspicious Child Process of Adobe Acrobat Reader Update Service", + "query": "event.category:process and event.type:(start or process_started) and process.parent.name:com.adobe.ARMDC.SMJobBlessHelper and user.name:root and not process.executable: (/Library/PrivilegedHelperTools/com.adobe.ARMDC.SMJobBlessHelper or /usr/bin/codesign or /private/var/folders/zz/*/T/download/ARMDCHammer or /usr/sbin/pkgutil or /usr/bin/shasum or /usr/bin/perl* or /usr/sbin/spctl or /usr/sbin/installer)", + "references": [ + "https://rekken.github.io/2020/05/14/Security-Flaws-in-Adobe-Acrobat-Reader-Allow-Malicious-Program-to-Gain-Root-on-macOS-Silently/" + ], + "risk_score": 73, + "rule_id": "f85ce03f-d8a8-4c83-acdc-5c8cd0592be7", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1068", + "name": "Exploitation for Privilege Escalation", + "reference": "https://attack.mitre.org/techniques/T1068/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json new file mode 100644 index 00000000000000..813265d95cb745 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modification of the dynamic linker preload shared object (ld.so.preload). Adversaries may execute malicious payloads by hijacking the dynamic linker used to load libraries.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Modification of Dynamic Linker Preload Shared Object", + "query": "event.category:file and not event.type:deletion and file.path:/etc/ld.so.preload", + "references": [ + "https://www.anomali.com/blog/rocke-evolves-its-arsenal-with-a-new-malware-family-written-in-golang" + ], + "risk_score": 47, + "rule_id": "717f82c2-7741-4f9b-85b8-d06aeb853f4f", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Privilege Escalation", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1574", + "name": "Hijack Execution Flow", + "reference": "https://attack.mitre.org/techniques/T1574/", + "subtechnique": [ + { + "id": "T1574.006", + "name": "LD_PRELOAD", + "reference": "https://attack.mitre.org/techniques/T1574/006/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json new file mode 100644 index 00000000000000..f303dbc81e6b24 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to add an account to the admin group via the command line. This could be an indication of privilege escalation activity.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Potential Admin Group Account Addition", + "query": "event.category:process and event.type:(start or process_started) and process.name:(dscl or dseditgroup) and process.args:((\"/Groups/admin\" or admin) and (\"-a\" or \"-append\"))", + "references": [ + "https://managingosx.wordpress.com/2010/01/14/add-a-user-to-the-admin-group-via-command-line-3-0/" + ], + "risk_score": 47, + "rule_id": "565c2b44-7a21-4818-955f-8d4737967d2e", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/", + "subtechnique": [ + { + "id": "T1078.003", + "name": "Local Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json new file mode 100644 index 00000000000000..10a5e3ba744c9e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json @@ -0,0 +1,75 @@ +{ + "author": [ + "Elastic" + ], + "description": "Adversaries can use the autostart mechanism provided by the Local Security Authority (LSA) authentication packages for privilege escalation or persistence by placing a reference to a binary in the Windows registry. The binary will then be executed by SYSTEM when the authentication packages are loaded.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential LSA Authentication Package Abuse", + "query": "registry where event.type == \"change\" and\n registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Lsa\\\\Authentication Packages\" and\n /* exclude SYSTEM SID - look for changes by non-SYSTEM user */\n not user.id : \"S-1-5-18\"\n", + "risk_score": 47, + "rule_id": "e9abe69b-1deb-4e19-ac4a-5d5ac00f72eb", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.002", + "name": "Authentication Package", + "reference": "https://attack.mitre.org/techniques/T1547/002/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.002", + "name": "Authentication Package", + "reference": "https://attack.mitre.org/techniques/T1547/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json index d78c5ba7a3814d..16a8cdf64ad0c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies a privilege escalation attempt via named pipe impersonation. An adversary may abuse this technique by utilizing a framework such Metasploit's meterpreter getsystem command.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -43,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json new file mode 100644 index 00000000000000..81248b7e0be1e0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json @@ -0,0 +1,84 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the loading of a non Microsoft signed DLL that is missing on a default Windows install (phantom DLL) or one that can be loaded from a different location by a native Windows process. This may be abused to persist or elevate privileges via privileged file write vulnerabilities.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Suspicious DLL Loaded for Persistence or Privilege Escalation", + "query": "library where dll.name :\n (\n \"wlbsctrl.dll\",\n \"wbemcomn.dll\",\n \"WptsExtensions.dll\",\n \"Tsmsisrv.dll\",\n \"TSVIPSrv.dll\",\n \"Msfte.dll\",\n \"wow64log.dll\",\n \"WindowsCoreDeviceInfo.dll\",\n \"Ualapi.dll\",\n \"wlanhlp.dll\",\n \"phoneinfo.dll\",\n \"EdgeGdi.dll\",\n \"cdpsgshims.dll\",\n \"windowsperformancerecordercontrol.dll\",\n \"diagtrack_win.dll\"\n ) and \nnot (dll.code_signature.subject_name : \"Microsoft Windows\" and dll.code_signature.status : \"trusted\")\n", + "references": [ + "https://itm4n.github.io/windows-dll-hijacking-clarified/", + "http://remoteawesomethoughts.blogspot.com/2019/05/windows-10-task-schedulerservice.html", + "https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html", + "https://shellz.club/edgegdi-dll-for-persistence-and-lateral-movement/", + "https://windows-internals.com/faxing-your-way-to-system/", + "http://waleedassar.blogspot.com/2013/01/wow64logdll.html" + ], + "risk_score": 73, + "rule_id": "bfeaf89b-a2a7-48a3-817f-e41829dc61ee", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Persistence", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1574", + "name": "Hijack Execution Flow", + "reference": "https://attack.mitre.org/techniques/T1574/", + "subtechnique": [ + { + "id": "T1574.002", + "name": "DLL Side-Loading", + "reference": "https://attack.mitre.org/techniques/T1574/002/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1574", + "name": "Hijack Execution Flow", + "reference": "https://attack.mitre.org/techniques/T1574/", + "subtechnique": [ + { + "id": "T1574.001", + "name": "DLL Search Order Hijacking", + "reference": "https://attack.mitre.org/techniques/T1574/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json new file mode 100644 index 00000000000000..6d72fa84b5f246 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json @@ -0,0 +1,78 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies port monitor and print processor registry modifications. Adversaries may abuse port monitor and print processors to run malicious DLLs during system boot that will be executed as SYSTEM for privilege escalation and/or persistence, if permissions allow writing a fully-qualified pathname for that DLL.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License", + "name": "Potential Port Monitor or Print Processor Registration Abuse", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path : (\"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Print\\\\Monitors\\\\*\",\n \"HLLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Print\\\\Environments\\\\Windows*\\\\Print Processors\\\\*\") and\n registry.data.strings : \"*.dll\" and\n /* exclude SYSTEM SID - look for changes by non-SYSTEM user */\n not user.id : \"S-1-5-18\"\n", + "references": [ + "https://www.welivesecurity.com/2020/05/21/no-game-over-winnti-group/" + ], + "risk_score": 47, + "rule_id": "8f3e91c7-d791-4704-80a1-42c160d7aa27", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.010", + "name": "Port Monitors", + "reference": "https://attack.mitre.org/techniques/T1547/010/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.010", + "name": "Port Monitors", + "reference": "https://attack.mitre.org/techniques/T1547/010/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json index 76b17b0d892294..7346e5f1e06db4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -17,7 +18,7 @@ "https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES/blob/master/Privilege%20Escalation/privesc_sysmon_cve_20201030_spooler.evtx", "https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-1030" ], - "risk_score": 74, + "risk_score": 73, "rule_id": "bd7eefee-f671-494e-98df-f01daf9e5f17", "severity": "high", "tags": [ @@ -45,5 +46,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json index 5a14984464bcb2..fae71bc2f98bbe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -16,7 +17,7 @@ "https://voidsec.com/cve-2020-1337-printdemon-is-dead-long-live-printdemon/", "https://www.thezdi.com/blog/2020/7/8/cve-2020-1300-remote-code-execution-through-microsoft-windows-cab-files" ], - "risk_score": 74, + "risk_score": 73, "rule_id": "5bb4a95d-5a08-48eb-80db-4c3a63ec78a8", "severity": "high", "tags": [ @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json index 3fcaea3c039e40..c6b7bb9838045b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -16,7 +17,7 @@ "references": [ "https://safebreach.com/Post/How-we-bypassed-CVE-2020-1048-Patch-and-got-CVE-2020-1337" ], - "risk_score": 74, + "risk_score": 73, "rule_id": "a7ccae7b-9d2c-44b2-a061-98e5946971fa", "severity": "high", "tags": [ @@ -45,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json index 3885aa3d847ca4..a160466cdc4d61 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json @@ -3,9 +3,11 @@ "Elastic" ], "description": "Identifies a privilege escalation attempt via a rogue Windows directory (Windir) environment variable. This is a known primitive that is often combined with other vulnerabilities to elevate privileges.", + "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -14,7 +16,7 @@ "references": [ "https://www.tiraniddo.dev/2017/05/exploiting-environment-variables-in.html" ], - "risk_score": 71, + "risk_score": 73, "rule_id": "d563aaba-2e72-462b-8658-3e5ea22db3a6", "severity": "high", "tags": [ @@ -50,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json new file mode 100644 index 00000000000000..4ca3c62012aded --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies modifications to the root crontab file. Adversaries may overwrite this file to gain code execution with root privileges by exploiting privileged file write or move related vulnerabilities.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Privilege Escalation via Root Crontab File Modification", + "query": "event.category:file and not event.type:deletion and file.path:/private/var/at/tabs/root and not process.executable:/usr/bin/crontab", + "references": [ + "https://phoenhex.re/2017-06-09/pwn2own-diskarbitrationd-privesc", + "https://www.exploit-db.com/exploits/42146" + ], + "risk_score": 73, + "rule_id": "0ff84c42-873d-41a2-a4ed-08d74d352d01", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1053", + "name": "Scheduled Task/Job", + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.003", + "name": "Cron", + "reference": "https://attack.mitre.org/techniques/T1053/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setgid_bit_set_via_chmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setgid_bit_set_via_chmod.json deleted file mode 100644 index 0bed7960781927..00000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setgid_bit_set_via_chmod.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "An adversary may add the setgid bit to a file or directory in order to run a file with the privileges of the owning group. An adversary can take advantage of this to either do a shell escape or exploit a vulnerability in an application with the setgid bit to get code running in a different user\u2019s context. Additionally, adversaries can use this mechanism on their own malware to make sure they're able to execute in elevated contexts in the future.", - "from": "now-9m", - "index": [ - "auditbeat-*", - "logs-endpoint.events.*" - ], - "language": "lucene", - "license": "Elastic License", - "max_signals": 33, - "name": "Setgid Bit Set via chmod", - "query": "event.category:process AND event.type:(start or process_started) AND process.name:chmod AND process.args:(g+s OR /2[0-9]{3}/) AND NOT user.name:root", - "risk_score": 21, - "rule_id": "3a86e085-094c-412d-97ff-2439731e59cb", - "severity": "low", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Privilege Escalation" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0004", - "name": "Privilege Escalation", - "reference": "https://attack.mitre.org/tactics/TA0004/" - }, - "technique": [ - { - "id": "T1548", - "name": "Abuse Elevation Control Mechanism", - "reference": "https://attack.mitre.org/techniques/T1548/", - "subtechnique": [ - { - "id": "T1548.001", - "name": "Setuid and Setgid", - "reference": "https://attack.mitre.org/techniques/T1548/001/" - } - ] - } - ] - }, - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0003", - "name": "Persistence", - "reference": "https://attack.mitre.org/tactics/TA0003/" - }, - "technique": [] - } - ], - "timestamp_override": "event.ingested", - "type": "query", - "version": 6 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_bit_set_via_chmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json similarity index 63% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_bit_set_via_chmod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json index a55f02a0af61aa..0501604f5c8405 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_bit_set_via_chmod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "An adversary may add the setuid bit to a file or directory in order to run a file with the privileges of the owning user. An adversary can take advantage of this to either do a shell escape or exploit a vulnerability in an application with the setuid bit to get code running in a different user\u2019s context. Additionally, adversaries can use this mechanism on their own malware to make sure they're able to execute in elevated contexts in the future.", + "description": "An adversary may add the setuid or setgid bit to a file or directory in order to run a file with the privileges of the owning user or group. An adversary can take advantage of this to either do a shell escape or exploit a vulnerability in an application with the setuid or setgid bit to get code running in a different user\u2019s context. Additionally, adversaries can use this mechanism on their own malware to make sure they're able to execute in elevated contexts in the future.", "from": "now-9m", "index": [ "auditbeat-*", @@ -11,8 +11,8 @@ "language": "lucene", "license": "Elastic License", "max_signals": 33, - "name": "Setuid Bit Set via chmod", - "query": "event.category:process AND event.type:(start or process_started) AND process.name:chmod AND process.args:(u+s OR /4[0-9]{3}/) AND NOT user.name:root", + "name": "Setuid / Setgid Bit Set via chmod", + "query": "event.category:process AND event.type:(start OR process_started) AND process.name:chmod AND process.args:(\"+s\" OR \"u+s\" OR /4[0-9]{3}/ OR g+s OR /2[0-9]{3}/)", "risk_score": 21, "rule_id": "8a1b0278-0f9a-487d-96bd-d4833298e87a", "severity": "low", @@ -20,6 +20,7 @@ "Elastic", "Host", "Linux", + "macOS", "Threat Detection", "Privilege Escalation" ], @@ -58,5 +59,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json new file mode 100644 index 00000000000000..f014318c04eaba --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json @@ -0,0 +1,58 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the attempted use of a heap-based buffer overflow vulnerability for the Sudo binary in Unix-like systems (CVE-2021-3156). Successful exploitation allows an unprivileged user to escalate to the root user.", + "false_positives": [ + "This rule could generate false positives if the process arguments leveraged by the exploit are shared by custom scripts using the Sudo or Sudoedit binaries. Only Sudo versions 1.8.2 through 1.8.31p2 and 1.9.0 through 1.9.5p1 are affected; if those versions are not present on the endpoint, this could be a false positive." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "kuery", + "license": "Elastic License", + "name": "Sudo Heap-Based Buffer Overflow Attempt", + "query": "event.category:process and event.type:start and process.name:(sudo or sudoedit) and process.args:(*\\\\ and (\"-i\" or \"-s\"))", + "references": [ + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=2021-3156", + "https://blog.qualys.com/vulnerabilities-research/2021/01/26/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit", + "https://www.bleepingcomputer.com/news/security/latest-macos-big-sur-also-has-sudo-root-privilege-escalation-flaw", + "https://www.sudo.ws/alerts/unescape_overflow.html" + ], + "risk_score": 73, + "rule_id": "f37f3054-d40b-49ac-aa9b-a786c74c58b8", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "macOS", + "Threat Detection", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1068", + "name": "Exploitation for Privilege Escalation", + "reference": "https://attack.mitre.org/techniques/T1068/" + } + ] + } + ], + "threshold": { + "field": "host.hostname", + "value": 100 + }, + "type": "threshold", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json index a49d2d89527ac9..2ac74bff3a69f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json @@ -11,14 +11,15 @@ "language": "kuery", "license": "Elastic License", "name": "Sudoers File Modification", - "query": "event.category:file and event.type:change and file.path:/etc/sudoers", - "risk_score": 21, + "query": "event.category:file and event.type:change and file.path:(/etc/sudoers* or /private/etc/sudoers*)", + "risk_score": 47, "rule_id": "931e25a5-0f5e-4ae0-ba0d-9e94eff7e3a4", - "severity": "low", + "severity": "medium", "tags": [ "Elastic", "Host", "Linux", + "macOS", "Threat Detection", "Privilege Escalation" ], @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json index 6bef3776153dbb..e028665bbdc907 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json @@ -6,16 +6,17 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "UAC Bypass Attempt with IEditionUpgradeManager Elevated COM Interface", - "query": "process where event.type in (\"start\", \"process_started\", \"info\") and process.name == \"Clipup.exe\" and \nprocess.executable != \"C:\\\\Windows\\\\System32\\\\ClipUp.exe\" and process.parent.name == \"dllhost.exe\" and\n /* CLSID of the Elevated COM Interface IEditionUpgradeManager */\n wildcard(process.parent.args,\"/Processid:{BD54C901-076B-434E-B6C7-17C531F4AB41}\")\n", + "query": "process where event.type in (\"start\", \"process_started\") and process.name : \"Clipup.exe\" and\n not process.executable : \"C:\\\\Windows\\\\System32\\\\ClipUp.exe\" and process.parent.name : \"dllhost.exe\" and\n /* CLSID of the Elevated COM Interface IEditionUpgradeManager */\n process.parent.args : \"/Processid:{BD54C901-076B-434E-B6C7-17C531F4AB41}\"\n", "references": [ "https://github.com/hfiref0x/UACME" ], - "risk_score": 71, + "risk_score": 73, "rule_id": "b90cdde7-7e0d-4359-8bf0-2c112ce2008a", "severity": "high", "tags": [ @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json index bcf916733d66b2..218322f4fdfa05 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json @@ -6,12 +6,13 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", "name": "UAC Bypass Attempt via Elevated COM Internet Explorer Add-On Installer", - "query": "process where event.type in (\"start\", \"process_started\", \"info\") and\n wildcard(process.executable, \"C:\\\\*\\\\AppData\\\\*\\\\Temp\\\\IDC*.tmp\\\\*.exe\") and\n process.parent.name == \"ieinstal.exe\" and process.parent.args == \"-Embedding\"\n\n /* uncomment once in winlogbeat */\n /* and not (process.code_signature.subject_name == \"Microsoft Corporation\" and process.code_signature.trusted == true) */\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.executable : \"C:\\\\*\\\\AppData\\\\*\\\\Temp\\\\IDC*.tmp\\\\*.exe\" and\n process.parent.name : \"ieinstal.exe\" and process.parent.args : \"-Embedding\"\n\n /* uncomment once in winlogbeat */\n /* and not (process.code_signature.subject_name == \"Microsoft Corporation\" and process.code_signature.trusted == true) */\n", "references": [ "https://swapcontext.blogspot.com/2020/11/uac-bypasses-from-comautoapprovallist.html" ], @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json index 81a796baa8824b..e9ba470cb2f35e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json index a81ff4214f5076..3dd864083b732b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json index 5b10fa84ea8695..bffbaad5136b42 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json index a73e6f4ccaa694..59f583e31dfd49 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "kuery", "license": "Elastic License", @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json index 76de6ef20f319e..3867fd918fae78 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -15,7 +16,7 @@ "references": [ "https://medium.com/tenable-techblog/uac-bypass-by-mocking-trusted-directories-24a96675f6e" ], - "risk_score": 71, + "risk_score": 73, "rule_id": "290aca65-e94d-403b-ba0f-62f320e63f51", "severity": "high", "tags": [ @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json index 983886377cf371..040c921f198a3b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -51,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json index 2cd74014708e9f..0854fe45cdf9d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json @@ -6,7 +6,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -52,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json index deaa05dc5f8d46..58e2f1ca3d81f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json @@ -9,7 +9,8 @@ "from": "now-9m", "index": [ "logs-endpoint.events.*", - "winlogbeat-*" + "winlogbeat-*", + "logs-windows.*" ], "language": "eql", "license": "Elastic License", @@ -66,5 +67,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } From 70d1d9cdbb713804a472bc760cc89bb42bd5d80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Thu, 18 Feb 2021 15:18:12 +0000 Subject: [PATCH 41/93] [Usage Collection] Small performance improvements (#91467) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/telemetry_collection/get_kibana.ts | 2 +- .../server/collector/collector_set.ts | 69 ++++++++++--------- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts index 35592d83cf822a..566c9428901502 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts @@ -77,5 +77,5 @@ export async function getKibana( kibanaRequest: KibanaRequest | undefined // intentionally `| undefined` to enforce providing the parameter ): Promise { const usage = await usageCollection.bulkFetch(asInternalUser, soClient, kibanaRequest); - return usageCollection.toObject(usage); + return usageCollection.toObject(usage); } diff --git a/src/plugins/usage_collection/server/collector/collector_set.ts b/src/plugins/usage_collection/server/collector/collector_set.ts index 9338af611758ea..32a58a6657eec2 100644 --- a/src/plugins/usage_collection/server/collector/collector_set.ts +++ b/src/plugins/usage_collection/server/collector/collector_set.ts @@ -211,46 +211,47 @@ export class CollectorSet { ); }; - // convert an array of fetched stats results into key/object - public toObject = (statsData: Array<{ type: string; result: T }> = []) => { - return statsData.reduce((accumulatedStats, { type, result }) => { - return { - ...accumulatedStats, - [type]: result, - }; - }, {} as Result); - }; - - // rename fields to use api conventions - public toApiFieldNames = (apiData: any): any => { - const getValueOrRecurse = (value: any) => { - if (value == null || typeof value !== 'object') { - return value; - } else { - return this.toApiFieldNames(value); // recurse - } - }; + /** + * Convert an array of fetched stats results into key/object + * @param statsData Array of fetched stats results + */ + public toObject, T = unknown>( + statsData: Array<{ type: string; result: T }> = [] + ): Result { + return Object.fromEntries(statsData.map(({ type, result }) => [type, result])) as Result; + } + /** + * Rename fields to use API conventions + * @param apiData Data to be normalized + */ + public toApiFieldNames( + apiData: Record | unknown[] + ): Record | unknown[] { // handle array and return early, or return a reduced object - if (Array.isArray(apiData)) { - return apiData.map(getValueOrRecurse); + return apiData.map((value) => this.getValueOrRecurse(value)); } - return Object.keys(apiData).reduce((accum, field) => { - const value = apiData[field]; - let newName = field; - newName = snakeCase(newName); - newName = newName.replace(/^(1|5|15)_m/, '$1m'); // os.load.15m, os.load.5m, os.load.1m - newName = newName.replace('_in_bytes', '_bytes'); - newName = newName.replace('_in_millis', '_ms'); + return Object.fromEntries( + Object.entries(apiData).map(([field, value]) => { + let newName = field; + newName = snakeCase(newName); + newName = newName.replace(/^(1|5|15)_m/, '$1m'); // os.load.15m, os.load.5m, os.load.1m + newName = newName.replace('_in_bytes', '_bytes'); + newName = newName.replace('_in_millis', '_ms'); - return { - ...accum, - [newName]: getValueOrRecurse(value), - }; - }, {}); - }; + return [newName, this.getValueOrRecurse(value)]; + }) + ); + } + + private getValueOrRecurse(value: unknown) { + if (Array.isArray(value) || (typeof value === 'object' && value !== null)) { + return this.toApiFieldNames(value as Record | unknown[]); // recurse + } + return value; + } private makeCollectorSetFromArray = (collectors: AnyCollector[]) => { return new CollectorSet({ From 543bf1bf1d86d81d13fd55c44e33f1d22268a0d9 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 18 Feb 2021 16:39:17 +0100 Subject: [PATCH 42/93] Discover: Add handling for source column (#91815) --- .../public/application/angular/context.js | 1 + .../application/angular/context_state.test.ts | 6 +++ .../application/angular/context_state.ts | 30 +++++++++---- .../public/application/angular/discover.js | 3 +- .../angular/discover_state.test.ts | 10 +++++ .../application/angular/discover_state.ts | 21 +++++++--- .../application/angular/helpers/index.ts | 1 + .../angular/helpers/state_helpers.ts | 42 +++++++++++++++++++ .../discover_grid/discover_grid.tsx | 3 +- .../functional/apps/discover/_shared_links.ts | 2 +- 10 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 src/plugins/discover/public/application/angular/helpers/state_helpers.ts diff --git a/src/plugins/discover/public/application/angular/context.js b/src/plugins/discover/public/application/angular/context.js index e4128b3e262473..01a28a5c174b6b 100644 --- a/src/plugins/discover/public/application/angular/context.js +++ b/src/plugins/discover/public/application/angular/context.js @@ -65,6 +65,7 @@ function ContextAppRouteController($routeParams, $scope, $route) { storeInSessionStorage: getServices().uiSettings.get('state:storeInSessionStorage'), history: getServices().history(), toasts: getServices().core.notifications.toasts, + uiSettings: getServices().core.uiSettings, }); this.state = { ...appState.getState() }; this.anchorId = $routeParams.id; diff --git a/src/plugins/discover/public/application/angular/context_state.test.ts b/src/plugins/discover/public/application/angular/context_state.test.ts index e7622a13394c13..b49a6546a993d3 100644 --- a/src/plugins/discover/public/application/angular/context_state.test.ts +++ b/src/plugins/discover/public/application/angular/context_state.test.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +import { IUiSettingsClient } from 'kibana/public'; import { getState } from './context_state'; import { createBrowserHistory, History } from 'history'; import { FilterManager, Filter } from '../../../../data/public'; import { coreMock } from '../../../../../core/public/mocks'; +import { SEARCH_FIELDS_FROM_SOURCE } from '../../../common'; const setupMock = coreMock.createSetup(); describe('Test Discover Context State', () => { @@ -23,6 +25,10 @@ describe('Test Discover Context State', () => { defaultStepSize: '4', timeFieldName: 'time', history, + uiSettings: { + get: (key: string) => + ((key === SEARCH_FIELDS_FROM_SOURCE ? true : ['_source']) as unknown) as T, + } as IUiSettingsClient, }); state.startSync(); }); diff --git a/src/plugins/discover/public/application/angular/context_state.ts b/src/plugins/discover/public/application/angular/context_state.ts index d109badbdd1646..0bae006ec1f6e4 100644 --- a/src/plugins/discover/public/application/angular/context_state.ts +++ b/src/plugins/discover/public/application/angular/context_state.ts @@ -8,7 +8,7 @@ import _ from 'lodash'; import { History } from 'history'; -import { NotificationsStart } from 'kibana/public'; +import { NotificationsStart, IUiSettingsClient } from 'kibana/public'; import { createStateContainer, createKbnUrlStateStorage, @@ -17,6 +17,7 @@ import { withNotifyOnErrors, } from '../../../../kibana_utils/public'; import { esFilters, FilterManager, Filter, Query } from '../../../../data/public'; +import { handleSourceColumnState } from './helpers'; export interface AppState { /** @@ -73,6 +74,11 @@ interface GetStateParams { * kbnUrlStateStorage will use it notifying about inner errors */ toasts?: NotificationsStart['toasts']; + + /** + * core ui settings service + */ + uiSettings: IUiSettingsClient; } interface GetStateReturn { @@ -123,6 +129,7 @@ export function getState({ storeInSessionStorage = false, history, toasts, + uiSettings, }: GetStateParams): GetStateReturn { const stateStorage = createKbnUrlStateStorage({ useHash: storeInSessionStorage, @@ -134,7 +141,12 @@ export function getState({ const globalStateContainer = createStateContainer(globalStateInitial); const appStateFromUrl = stateStorage.get(APP_STATE_URL_KEY) as AppState; - const appStateInitial = createInitialAppState(defaultStepSize, timeFieldName, appStateFromUrl); + const appStateInitial = createInitialAppState( + defaultStepSize, + timeFieldName, + appStateFromUrl, + uiSettings + ); const appStateContainer = createStateContainer(appStateInitial); const { start, stop } = syncStates([ @@ -257,7 +269,8 @@ function getFilters(state: AppState | GlobalState): Filter[] { function createInitialAppState( defaultSize: string, timeFieldName: string, - urlState: AppState + urlState: AppState, + uiSettings: IUiSettingsClient ): AppState { const defaultState = { columns: ['_source'], @@ -270,8 +283,11 @@ function createInitialAppState( return defaultState; } - return { - ...defaultState, - ...urlState, - }; + return handleSourceColumnState( + { + ...defaultState, + ...urlState, + }, + uiSettings + ); } diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index c2bbbf2e57a9ad..78ad40e48fd965 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -110,7 +110,7 @@ app.config(($routeProvider) => { const history = getHistory(); const savedSearchId = $route.current.params.id; return data.indexPatterns.ensureDefaultIndexPattern(history).then(() => { - const { appStateContainer } = getState({ history }); + const { appStateContainer } = getState({ history, uiSettings: config }); const { index } = appStateContainer.getState(); return Promise.props({ ip: loadIndexPattern(index, data.indexPatterns, config), @@ -195,6 +195,7 @@ function discoverController($route, $scope, Promise) { storeInSessionStorage: config.get('state:storeInSessionStorage'), history, toasts: core.notifications.toasts, + uiSettings: config, }); const { diff --git a/src/plugins/discover/public/application/angular/discover_state.test.ts b/src/plugins/discover/public/application/angular/discover_state.test.ts index d3b7ed53e421af..e7322a85886311 100644 --- a/src/plugins/discover/public/application/angular/discover_state.test.ts +++ b/src/plugins/discover/public/application/angular/discover_state.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { IUiSettingsClient } from 'kibana/public'; import { getState, GetStateReturn, @@ -14,11 +15,17 @@ import { import { createBrowserHistory, History } from 'history'; import { dataPluginMock } from '../../../../data/public/mocks'; import { SavedSearch } from '../../saved_searches'; +import { SEARCH_FIELDS_FROM_SOURCE } from '../../../common'; let history: History; let state: GetStateReturn; const getCurrentUrl = () => history.createHref(history.location); +const uiSettingsMock = { + get: (key: string) => + ((key === SEARCH_FIELDS_FROM_SOURCE ? true : ['_source']) as unknown) as T, +} as IUiSettingsClient; + describe('Test discover state', () => { beforeEach(async () => { history = createBrowserHistory(); @@ -26,6 +33,7 @@ describe('Test discover state', () => { state = getState({ getStateDefaults: () => ({ index: 'test' }), history, + uiSettings: uiSettingsMock, }); await state.replaceUrlAppState({}); await state.startSync(); @@ -81,6 +89,7 @@ describe('Test discover state with legacy migration', () => { state = getState({ getStateDefaults: () => ({ index: 'test' }), history, + uiSettings: uiSettingsMock, }); expect(state.appStateContainer.getState()).toMatchInlineSnapshot(` Object { @@ -106,6 +115,7 @@ describe('createSearchSessionRestorationDataProvider', () => { data: mockDataPlugin, appStateContainer: getState({ history: createBrowserHistory(), + uiSettings: uiSettingsMock, }).appStateContainer, getSavedSearch: () => mockSavedSearch, }); diff --git a/src/plugins/discover/public/application/angular/discover_state.ts b/src/plugins/discover/public/application/angular/discover_state.ts index 93fc49b65cbc92..e7d5ed469525f7 100644 --- a/src/plugins/discover/public/application/angular/discover_state.ts +++ b/src/plugins/discover/public/application/angular/discover_state.ts @@ -9,7 +9,7 @@ import { isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; import { History } from 'history'; -import { NotificationsStart } from 'kibana/public'; +import { NotificationsStart, IUiSettingsClient } from 'kibana/public'; import { createKbnUrlStateStorage, createStateContainer, @@ -30,6 +30,7 @@ import { migrateLegacyQuery } from '../helpers/migrate_legacy_query'; import { DiscoverGridSettings } from '../components/discover_grid/types'; import { DISCOVER_APP_URL_GENERATOR, DiscoverUrlGeneratorState } from '../../url_generator'; import { SavedSearch } from '../../saved_searches'; +import { handleSourceColumnState } from './helpers'; export interface AppState { /** @@ -90,6 +91,11 @@ interface GetStateParams { * kbnUrlStateStorage will use it notifying about inner errors */ toasts?: NotificationsStart['toasts']; + + /** + * core ui settings service + */ + uiSettings: IUiSettingsClient; } export interface GetStateReturn { @@ -149,6 +155,7 @@ export function getState({ storeInSessionStorage = false, history, toasts, + uiSettings, }: GetStateParams): GetStateReturn { const defaultAppState = getStateDefaults ? getStateDefaults() : {}; const stateStorage = createKbnUrlStateStorage({ @@ -163,10 +170,14 @@ export function getState({ appStateFromUrl.query = migrateLegacyQuery(appStateFromUrl.query); } - let initialAppState = { - ...defaultAppState, - ...appStateFromUrl, - }; + let initialAppState = handleSourceColumnState( + { + ...defaultAppState, + ...appStateFromUrl, + }, + uiSettings + ); + // todo filter source depending on fields fetchinbg flag (if no columns remain and source fetching is enabled, use default columns) let previousAppState: AppState; const appStateContainer = createStateContainer(initialAppState); diff --git a/src/plugins/discover/public/application/angular/helpers/index.ts b/src/plugins/discover/public/application/angular/helpers/index.ts index a4ab9e575e4217..3d4893268fdee8 100644 --- a/src/plugins/discover/public/application/angular/helpers/index.ts +++ b/src/plugins/discover/public/application/angular/helpers/index.ts @@ -8,3 +8,4 @@ export { buildPointSeriesData } from './point_series'; export { formatRow } from './row_formatter'; +export { handleSourceColumnState } from './state_helpers'; diff --git a/src/plugins/discover/public/application/angular/helpers/state_helpers.ts b/src/plugins/discover/public/application/angular/helpers/state_helpers.ts new file mode 100644 index 00000000000000..ec5009dcf48392 --- /dev/null +++ b/src/plugins/discover/public/application/angular/helpers/state_helpers.ts @@ -0,0 +1,42 @@ +/* + * 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 { IUiSettingsClient } from 'src/core/public'; +import { SEARCH_FIELDS_FROM_SOURCE, DEFAULT_COLUMNS_SETTING } from '../../../../common'; + +/** + * Makes sure the current state is not referencing the source column when using the fields api + * @param state + * @param uiSettings + */ +export function handleSourceColumnState( + state: TState, + uiSettings: IUiSettingsClient +): TState { + if (!state.columns) { + return state; + } + const useNewFieldsApi = !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); + const defaultColumns = uiSettings.get(DEFAULT_COLUMNS_SETTING); + if (useNewFieldsApi) { + // if fields API is used, filter out the source column + return { + ...state, + columns: state.columns.filter((column) => column !== '_source'), + }; + } else if (state.columns.length === 0) { + // if _source fetching is used and there are no column, switch back to default columns + // this can happen if the fields API was previously used + return { + ...state, + columns: [...defaultColumns], + }; + } + + return state; +} diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx index a4e59a50a2f6c7..fcaea63f2a77c4 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -315,7 +315,8 @@ export const DiscoverGrid = ({ Date: Thu, 18 Feb 2021 17:15:22 +0100 Subject: [PATCH 43/93] [Lens] Support index pattern runtime fields in existence and field stats API (#90600) --- .../field_item.test.tsx | 115 +++----- .../indexpattern_datasource/field_item.tsx | 5 +- .../server/routes/existing_fields.test.ts | 27 ++ .../lens/server/routes/existing_fields.ts | 10 +- .../plugins/lens/server/routes/field_stats.ts | 43 +-- .../api_integration/apis/lens/field_stats.ts | 253 +++++++++--------- .../es_archives/visualize/default/data.json | 25 ++ 7 files changed, 250 insertions(+), 228 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx index fca958a39b086f..0871ef47494960 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx @@ -116,27 +116,6 @@ describe('IndexPattern Field Item', () => { ); }); - it('should request field stats without a time field, if the index pattern has none', async () => { - indexPattern.timeFieldName = undefined; - core.http.post.mockImplementationOnce(() => { - return Promise.resolve({}); - }); - const wrapper = mountWithIntl(); - - await act(async () => { - clickField(wrapper, 'bytes'); - }); - - expect(core.http.post).toHaveBeenCalledWith( - '/api/lens/index_stats/my-fake-index-pattern/field', - expect.anything() - ); - // Function argument types not detected correctly (https://github.com/microsoft/TypeScript/issues/26591) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { body } = (core.http.post.mock.calls[0] as any)[1]; - expect(JSON.parse(body)).not.toHaveProperty('timeFieldName'); - }); - it('should request field stats every time the button is clicked', async () => { let resolveFunction: (arg: unknown) => void; @@ -150,31 +129,21 @@ describe('IndexPattern Field Item', () => { clickField(wrapper, 'bytes'); - expect(core.http.post).toHaveBeenCalledWith( - `/api/lens/index_stats/my-fake-index-pattern/field`, - { - body: JSON.stringify({ - dslQuery: { - bool: { - must: [{ match_all: {} }], - filter: [], - should: [], - must_not: [], - }, + expect(core.http.post).toHaveBeenCalledWith(`/api/lens/index_stats/1/field`, { + body: JSON.stringify({ + dslQuery: { + bool: { + must: [{ match_all: {} }], + filter: [], + should: [], + must_not: [], }, - fromDate: 'now-7d', - toDate: 'now', - timeFieldName: 'timestamp', - field: { - name: 'bytes', - displayName: 'bytesLabel', - type: 'number', - aggregatable: true, - searchable: true, - }, - }), - } - ); + }, + fromDate: 'now-7d', + toDate: 'now', + fieldName: 'bytes', + }), + }); expect(wrapper.find(EuiPopover).prop('isOpen')).toEqual(true); @@ -227,40 +196,30 @@ describe('IndexPattern Field Item', () => { clickField(wrapper, 'bytes'); expect(core.http.post).toHaveBeenCalledTimes(2); - expect(core.http.post).toHaveBeenLastCalledWith( - `/api/lens/index_stats/my-fake-index-pattern/field`, - { - body: JSON.stringify({ - dslQuery: { - bool: { - must: [], - filter: [ - { - bool: { - should: [{ match_phrase: { 'geo.src': 'US' } }], - minimum_should_match: 1, - }, - }, - { - match: { phrase: { 'geo.dest': 'US' } }, + expect(core.http.post).toHaveBeenLastCalledWith(`/api/lens/index_stats/1/field`, { + body: JSON.stringify({ + dslQuery: { + bool: { + must: [], + filter: [ + { + bool: { + should: [{ match_phrase: { 'geo.src': 'US' } }], + minimum_should_match: 1, }, - ], - should: [], - must_not: [], - }, + }, + { + match: { phrase: { 'geo.dest': 'US' } }, + }, + ], + should: [], + must_not: [], }, - fromDate: 'now-14d', - toDate: 'now-7d', - timeFieldName: 'timestamp', - field: { - name: 'bytes', - displayName: 'bytesLabel', - type: 'number', - aggregatable: true, - searchable: true, - }, - }), - } - ); + }, + fromDate: 'now-14d', + toDate: 'now-7d', + fieldName: 'bytes', + }), + }); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index e0198d6d7903e7..e5d46b4a7a0737 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -129,7 +129,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { setState((s) => ({ ...s, isLoading: true })); core.http - .post(`/api/lens/index_stats/${indexPattern.title}/field`, { + .post(`/api/lens/index_stats/${indexPattern.id}/field`, { body: JSON.stringify({ dslQuery: esQuery.buildEsQuery( indexPattern as IIndexPattern, @@ -139,8 +139,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { ), fromDate: dateRange.fromDate, toDate: dateRange.toDate, - timeFieldName: indexPattern.timeFieldName, - field, + fieldName: field.name, }), }) .then((results: FieldStatsResponse) => { diff --git a/x-pack/plugins/lens/server/routes/existing_fields.test.ts b/x-pack/plugins/lens/server/routes/existing_fields.test.ts index c6364eca0ff499..3f3e94099f6669 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.test.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.test.ts @@ -61,6 +61,20 @@ describe('existingFields', () => { expect(result).toEqual(['bar']); }); + it('supports runtime fields', () => { + const result = existingFields( + [searchResults({ runtime_foo: ['scriptvalue'] })], + [ + field({ + name: 'runtime_foo', + runtimeField: { type: 'long', script: { source: '2+2' } }, + }), + ] + ); + + expect(result).toEqual(['runtime_foo']); + }); + it('supports meta fields', () => { const result = existingFields( [{ _mymeta: 'abc', ...searchResults({ bar: ['scriptvalue'] }) }], @@ -78,6 +92,11 @@ describe('buildFieldList', () => { typeMeta: 'typemeta', fields: [ { name: 'foo', scripted: true, lang: 'painless', script: '2+2' }, + { + name: 'runtime_foo', + isMapped: false, + runtimeField: { type: 'long', script: { source: '2+2' } }, + }, { name: 'bar' }, { name: '@bar' }, { name: 'baz' }, @@ -95,6 +114,14 @@ describe('buildFieldList', () => { }); }); + it('supports runtime fields', () => { + const fields = buildFieldList((indexPattern as unknown) as IndexPattern, []); + expect(fields.find((f) => f.runtimeField)).toMatchObject({ + name: 'runtime_foo', + runtimeField: { type: 'long', script: { source: '2+2' } }, + }); + }); + it('supports meta fields', () => { const fields = buildFieldList((indexPattern as unknown) as IndexPattern, ['_mymeta']); expect(fields.find((f) => f.isMeta)).toMatchObject({ diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index e76abf4598efa9..11db9360749eaf 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -10,7 +10,7 @@ import { errors } from '@elastic/elasticsearch'; import { schema } from '@kbn/config-schema'; import { RequestHandlerContext, ElasticsearchClient } from 'src/core/server'; import { CoreSetup, Logger } from 'src/core/server'; -import { IndexPattern, IndexPatternsService } from 'src/plugins/data/common'; +import { IndexPattern, IndexPatternsService, RuntimeField } from 'src/plugins/data/common'; import { BASE_API_URL } from '../../common'; import { UI_SETTINGS } from '../../../../../src/plugins/data/server'; import { PluginStartContract } from '../plugin'; @@ -30,6 +30,7 @@ export interface Field { isMeta: boolean; lang?: string; script?: string; + runtimeField?: RuntimeField; } export async function existingFieldsRoute(setup: CoreSetup, logger: Logger) { @@ -138,6 +139,7 @@ export function buildFieldList(indexPattern: IndexPattern, metaFields: string[]) // id is a special case - it doesn't show up in the meta field list, // but as it's not part of source, it has to be handled separately. isMeta: metaFields.includes(field.name) || field.name === '_id', + runtimeField: !field.isMapped ? field.runtimeField : undefined, }; }); } @@ -181,6 +183,7 @@ async function fetchIndexPatternStats({ }; const scriptedFields = fields.filter((f) => f.isScript); + const runtimeFields = fields.filter((f) => f.runtimeField); const { body: result } = await client.search({ index, body: { @@ -189,6 +192,11 @@ async function fetchIndexPatternStats({ sort: timeFieldName && fromDate && toDate ? [{ [timeFieldName]: 'desc' }] : [], fields: ['*'], _source: false, + runtime_mappings: runtimeFields.reduce((acc, field) => { + if (!field.runtimeField) return acc; + acc[field.name] = field.runtimeField; + return acc; + }, {} as Record), script_fields: scriptedFields.reduce((acc, field) => { acc[field.name] = { script: { diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index 7fd884755d86df..9094e5442dc511 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -11,6 +11,7 @@ import DateMath from '@elastic/datemath'; import { schema } from '@kbn/config-schema'; import { CoreSetup } from 'src/core/server'; import { IFieldType } from 'src/plugins/data/common'; +import { SavedObjectNotFound } from '../../../../../src/plugins/kibana_utils/common'; import { ESSearchResponse } from '../../../../typings/elasticsearch'; import { FieldStatsResponse, BASE_API_URL } from '../../common'; import { PluginStartContract } from '../plugin'; @@ -21,28 +22,17 @@ export async function initFieldsRoute(setup: CoreSetup) { const router = setup.http.createRouter(); router.post( { - path: `${BASE_API_URL}/index_stats/{indexPatternTitle}/field`, + path: `${BASE_API_URL}/index_stats/{indexPatternId}/field`, validate: { params: schema.object({ - indexPatternTitle: schema.string(), + indexPatternId: schema.string(), }), body: schema.object( { dslQuery: schema.object({}, { unknowns: 'allow' }), fromDate: schema.string(), toDate: schema.string(), - timeFieldName: schema.maybe(schema.string()), - field: schema.object( - { - name: schema.string(), - type: schema.string(), - esTypes: schema.maybe(schema.arrayOf(schema.string())), - scripted: schema.maybe(schema.boolean()), - lang: schema.maybe(schema.string()), - script: schema.maybe(schema.string()), - }, - { unknowns: 'allow' } - ), + fieldName: schema.string(), }, { unknowns: 'allow' } ), @@ -50,9 +40,26 @@ export async function initFieldsRoute(setup: CoreSetup) { }, async (context, req, res) => { const requestClient = context.core.elasticsearch.client.asCurrentUser; - const { fromDate, toDate, timeFieldName, field, dslQuery } = req.body; + const { fromDate, toDate, fieldName, dslQuery } = req.body; + + const [{ savedObjects, elasticsearch }, { data }] = await setup.getStartServices(); + const savedObjectsClient = savedObjects.getScopedClient(req); + const esClient = elasticsearch.client.asScoped(req).asCurrentUser; + const indexPatternsService = await data.indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + esClient + ); try { + const indexPattern = await indexPatternsService.get(req.params.indexPatternId); + + const timeFieldName = indexPattern.timeFieldName; + const field = indexPattern.fields.find((f) => f.name === fieldName); + + if (!field) { + throw new Error(`Field {fieldName} not found in index pattern ${indexPattern.title}`); + } + const filter = timeFieldName ? [ { @@ -75,11 +82,12 @@ export async function initFieldsRoute(setup: CoreSetup) { const search = async (aggs: unknown) => { const { body: result } = await requestClient.search({ - index: req.params.indexPatternTitle, + index: indexPattern.title, track_total_hits: true, body: { query, aggs, + runtime_mappings: field.runtimeField ? { [fieldName]: field.runtimeField } : {}, }, size: 0, }); @@ -104,6 +112,9 @@ export async function initFieldsRoute(setup: CoreSetup) { body: await getStringSamples(search, field), }); } catch (e) { + if (e instanceof SavedObjectNotFound) { + return res.notFound(); + } if (e instanceof errors.ResponseError && e.statusCode === 404) { return res.notFound(); } diff --git a/x-pack/test/api_integration/apis/lens/field_stats.ts b/x-pack/test/api_integration/apis/lens/field_stats.ts index ac4ebb4e5b02c2..94960b98591219 100644 --- a/x-pack/test/api_integration/apis/lens/field_stats.ts +++ b/x-pack/test/api_integration/apis/lens/field_stats.ts @@ -22,29 +22,29 @@ export default ({ getService }: FtrProviderContext) => { describe('index stats apis', () => { before(async () => { await esArchiver.loadIfNeeded('logstash_functional'); - await esArchiver.loadIfNeeded('visualize/default'); - await esArchiver.loadIfNeeded('pre_calculated_histogram'); }); after(async () => { await esArchiver.unload('logstash_functional'); - await esArchiver.unload('visualize/default'); - await esArchiver.unload('pre_calculated_histogram'); }); describe('field distribution', () => { + before(async () => { + await esArchiver.loadIfNeeded('visualize/default'); + }); + after(async () => { + await esArchiver.unload('visualize/default'); + }); + it('should return a 404 for missing index patterns', async () => { await supertest - .post('/api/lens/index_stats/logstash/field') + .post('/api/lens/index_stats/123/field') .set(COMMON_HEADERS) .send({ dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, timeFieldName: '@timestamp', - field: { - name: 'bytes', - type: 'number', - }, + fieldName: 'bytes', }) .expect(404); }); @@ -57,10 +57,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - field: { - name: 'bytes', - type: 'number', - }, + fieldName: 'bytes', }) .expect(200); @@ -75,11 +72,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'bytes', - type: 'number', - }, + fieldName: 'bytes', }) .expect(200); @@ -186,11 +179,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: '@timestamp', - type: 'date', - }, + fieldName: '@timestamp', }) .expect(200); @@ -223,11 +212,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'geo.src', - type: 'string', - }, + fieldName: 'geo.src', }) .expect(200); @@ -290,11 +275,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'ip', - type: 'ip', - }, + fieldName: 'ip', }) .expect(200); @@ -349,6 +330,113 @@ export default ({ getService }: FtrProviderContext) => { }); }); + it('should return histograms for scripted date fields', async () => { + const { body } = await supertest + .post('/api/lens/index_stats/logstash-2015.09.22/field') + .set(COMMON_HEADERS) + .send({ + dslQuery: { match_all: {} }, + fromDate: TEST_START_TIME, + toDate: TEST_END_TIME, + fieldName: 'scripted_date', + }) + .expect(200); + + expect(body).to.eql({ + histogram: { + buckets: [ + { + count: 4634, + key: 0, + }, + ], + }, + totalDocuments: 4634, + }); + }); + + it('should return top values for scripted string fields', async () => { + const { body } = await supertest + .post('/api/lens/index_stats/logstash-2015.09.22/field') + .set(COMMON_HEADERS) + .send({ + dslQuery: { match_all: {} }, + fromDate: TEST_START_TIME, + toDate: TEST_END_TIME, + fieldName: 'scripted_string', + }) + .expect(200); + + expect(body).to.eql({ + totalDocuments: 4634, + sampledDocuments: 4634, + sampledValues: 4634, + topValues: { + buckets: [ + { + count: 4634, + key: 'hello', + }, + ], + }, + }); + }); + + it('should return top values for index pattern runtime string fields', async () => { + const { body } = await supertest + .post('/api/lens/index_stats/logstash-2015.09.22/field') + .set(COMMON_HEADERS) + .send({ + dslQuery: { match_all: {} }, + fromDate: TEST_START_TIME, + toDate: TEST_END_TIME, + fieldName: 'runtime_string_field', + }) + .expect(200); + + expect(body).to.eql({ + totalDocuments: 4634, + sampledDocuments: 4634, + sampledValues: 4634, + topValues: { + buckets: [ + { + count: 4634, + key: 'hello world!', + }, + ], + }, + }); + }); + + it('should apply filters and queries', async () => { + const { body } = await supertest + .post('/api/lens/index_stats/logstash-2015.09.22/field') + .set(COMMON_HEADERS) + .send({ + dslQuery: { + bool: { + filter: [{ match: { 'geo.src': 'US' } }], + }, + }, + fromDate: TEST_START_TIME, + toDate: TEST_END_TIME, + fieldName: 'bytes', + }) + .expect(200); + + expect(body.totalDocuments).to.eql(425); + }); + }); + + describe('histogram', () => { + before(async () => { + await esArchiver.loadIfNeeded('pre_calculated_histogram'); + }); + after(async () => { + await esArchiver.unload('pre_calculated_histogram'); + }); + it('should return an auto histogram for precalculated histograms', async () => { const { body } = await supertest .post('/api/lens/index_stats/histogram-test/field') @@ -357,10 +445,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match_all: {} }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - field: { - name: 'histogram-content', - type: 'histogram', - }, + fieldName: 'histogram-content', }) .expect(200); @@ -428,10 +513,7 @@ export default ({ getService }: FtrProviderContext) => { dslQuery: { match: { 'histogram-title': 'single value' } }, fromDate: TEST_START_TIME, toDate: TEST_END_TIME, - field: { - name: 'histogram-content', - type: 'histogram', - }, + fieldName: 'histogram-content', }) .expect(200); @@ -443,95 +525,6 @@ export default ({ getService }: FtrProviderContext) => { topValues: { buckets: [] }, }); }); - - it('should return histograms for scripted date fields', async () => { - const { body } = await supertest - .post('/api/lens/index_stats/logstash-2015.09.22/field') - .set(COMMON_HEADERS) - .send({ - dslQuery: { match_all: {} }, - fromDate: TEST_START_TIME, - toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'scripted date', - type: 'date', - scripted: true, - script: '1234', - lang: 'painless', - }, - }) - .expect(200); - - expect(body).to.eql({ - histogram: { - buckets: [ - { - count: 4634, - key: 0, - }, - ], - }, - totalDocuments: 4634, - }); - }); - - it('should return top values for scripted string fields', async () => { - const { body } = await supertest - .post('/api/lens/index_stats/logstash-2015.09.22/field') - .set(COMMON_HEADERS) - .send({ - dslQuery: { match_all: {} }, - fromDate: TEST_START_TIME, - toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'scripted string', - type: 'string', - scripted: true, - script: 'return "hello"', - lang: 'painless', - }, - }) - .expect(200); - - expect(body).to.eql({ - totalDocuments: 4634, - sampledDocuments: 4634, - sampledValues: 4634, - topValues: { - buckets: [ - { - count: 4634, - key: 'hello', - }, - ], - }, - }); - }); - - it('should apply filters and queries', async () => { - const { body } = await supertest - .post('/api/lens/index_stats/logstash-2015.09.22/field') - .set(COMMON_HEADERS) - .send({ - dslQuery: { - bool: { - filter: [{ match: { 'geo.src': 'US' } }], - }, - }, - fromDate: TEST_START_TIME, - toDate: TEST_END_TIME, - timeFieldName: '@timestamp', - field: { - name: 'bytes', - type: 'number', - }, - }) - .expect(200); - - expect(body.totalDocuments).to.eql(425); - }); }); }); }; diff --git a/x-pack/test/functional/es_archives/visualize/default/data.json b/x-pack/test/functional/es_archives/visualize/default/data.json index 26b033e28b4da7..7d0ad0c25f96db 100644 --- a/x-pack/test/functional/es_archives/visualize/default/data.json +++ b/x-pack/test/functional/es_archives/visualize/default/data.json @@ -145,6 +145,31 @@ } } +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "index-pattern:logstash-2015.09.22", + "index": ".kibana_1", + "source": { + "index-pattern": { + "timeFieldName": "@timestamp", + "title": "logstash-2015.09.22", + "fields":"[{\"name\":\"scripted_date\",\"type\":\"date\",\"count\":0,\"scripted\":true,\"script\":\"1234\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"scripted_string\",\"type\":\"string\",\"count\":0,\"scripted\":true,\"script\":\"return 'hello'\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", + "runtimeFieldMap":"{\"runtime_string_field\":{\"type\":\"keyword\",\"script\":{\"source\":\"emit('hello world!')\"}}}" + }, + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [ + ], + "type": "index-pattern", + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + { "type": "doc", "value": { From c5dabc2e86c4ab7a8ae7f9c7a747538985f54a71 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Thu, 18 Feb 2021 16:25:57 +0000 Subject: [PATCH 44/93] [Discover] Always request unmapped fields in a single doc view and a context view (#91825) * [Discover] Always request unmapped fields in a single doc view * Updating unit test * Request unmapped fields in context view --- .../public/application/angular/context/api/anchor.js | 2 +- .../public/application/angular/context/api/anchor.test.js | 2 +- .../public/application/angular/context/api/context.ts | 2 +- .../application/components/doc/use_es_doc_search.test.tsx | 5 ++++- .../public/application/components/doc/use_es_doc_search.ts | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/plugins/discover/public/application/angular/context/api/anchor.js b/src/plugins/discover/public/application/angular/context/api/anchor.js index f1074be2ebcf25..83b611cb0d6488 100644 --- a/src/plugins/discover/public/application/angular/context/api/anchor.js +++ b/src/plugins/discover/public/application/angular/context/api/anchor.js @@ -32,7 +32,7 @@ export function fetchAnchorProvider(indexPatterns, searchSource, useNewFieldsApi .setField('sort', sort); if (useNewFieldsApi) { searchSource.removeField('fieldsFromSource'); - searchSource.setField('fields', ['*']); + searchSource.setField('fields', [{ field: '*', include_unmapped: 'true' }]); } const response = await searchSource.fetch(); diff --git a/src/plugins/discover/public/application/angular/context/api/anchor.test.js b/src/plugins/discover/public/application/angular/context/api/anchor.test.js index 5b077f7443c27e..12b9b4ab285567 100644 --- a/src/plugins/discover/public/application/angular/context/api/anchor.test.js +++ b/src/plugins/discover/public/application/angular/context/api/anchor.test.js @@ -154,7 +154,7 @@ describe('context app', function () { const removeFieldsSpy = searchSourceStub.removeField.withArgs('fieldsFromSource'); expect(setFieldsSpy.calledOnce).toBe(true); expect(removeFieldsSpy.calledOnce).toBe(true); - expect(setFieldsSpy.firstCall.args[1]).toEqual(['*']); + expect(setFieldsSpy.firstCall.args[1]).toEqual([{ field: '*', include_unmapped: 'true' }]); }); }); }); diff --git a/src/plugins/discover/public/application/angular/context/api/context.ts b/src/plugins/discover/public/application/angular/context/api/context.ts index 4887a0644b6eb6..43f6e83d286b36 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.ts @@ -114,7 +114,7 @@ function fetchContextProvider(indexPatterns: IndexPatternsContract, useNewFields const searchSource = await data.search.searchSource.create(); if (useNewFieldsApi) { searchSource.removeField('fieldsFromSource'); - searchSource.setField('fields', ['*']); + searchSource.setField('fields', [{ field: '*', include_unmapped: 'true' }]); } return searchSource .setParent(undefined) diff --git a/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx b/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx index ef2619070a6d8e..dc0b6416a7f576 100644 --- a/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx +++ b/src/plugins/discover/public/application/components/doc/use_es_doc_search.test.tsx @@ -67,7 +67,10 @@ describe('Test of helper / hook', () => { "_source": false, "docvalue_fields": Array [], "fields": Array [ - "*", + Object { + "field": "*", + "include_unmapped": "true", + }, ], "query": Object { "ids": Object { diff --git a/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts b/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts index 295b2ab3831194..703a1e557e801a 100644 --- a/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts +++ b/src/plugins/discover/public/application/components/doc/use_es_doc_search.ts @@ -39,7 +39,7 @@ export function buildSearchBody( }, stored_fields: computedFields.storedFields, _source: !useNewFieldsApi, - fields: useNewFieldsApi ? ['*'] : undefined, + fields: useNewFieldsApi ? [{ field: '*', include_unmapped: 'true' }] : undefined, script_fields: computedFields.scriptFields, docvalue_fields: computedFields.docvalueFields, }; From 386afdca8ffc8b5c61aa0c2ce0ea1a3476fd0aa7 Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Thu, 18 Feb 2021 18:34:44 +0200 Subject: [PATCH 45/93] [SOM] fix flaky suites (#91809) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- test/functional/apps/management/_import_objects.ts | 1 - .../apps/saved_objects_management/edit_saved_object.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts index ca8d8c392ce49b..a3daaf86294939 100644 --- a/test/functional/apps/management/_import_objects.ts +++ b/test/functional/apps/management/_import_objects.ts @@ -23,7 +23,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const log = getService('log'); - // FLAKY: https://github.com/elastic/kibana/issues/89478 describe('import objects', function describeIndexTests() { describe('.ndjson file', () => { beforeEach(async function () { diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts index 81569c5bfc498a..89889088bd73ba 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -55,8 +55,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await button.click(); }; - // Flaky: https://github.com/elastic/kibana/issues/68400 - describe.skip('saved objects edition page', () => { + describe('saved objects edition page', () => { beforeEach(async () => { await esArchiver.load('saved_objects_management/edit_saved_object'); }); From 47b2c12b7ec28a5745b13ce6a1054dd810520b3a Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Thu, 18 Feb 2021 12:23:30 -0500 Subject: [PATCH 46/93] [backportrc] Adds 7.12 branch and bumps 7.x (#91883) --- .backportrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.backportrc.json b/.backportrc.json index 2752768194e0f6..384e221329a4f6 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -3,6 +3,7 @@ "targetBranchChoices": [ { "name": "master", "checked": true }, { "name": "7.x", "checked": true }, + "7.12", "7.11", "7.10", "7.9", @@ -29,7 +30,7 @@ "targetPRLabels": ["backport"], "branchLabelMapping": { "^v8.0.0$": "master", - "^v7.12.0$": "7.x", + "^v7.13.0$": "7.x", "^v(\\d+).(\\d+).\\d+$": "$1.$2" }, "autoMerge": true, From 4304cb9e625148c1cbe1610d2a91490d25a7b931 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 18 Feb 2021 17:30:53 +0000 Subject: [PATCH 47/93] chore(NA): setup backport tool for new 7.12 branch (#91858) From 5342877a32b213ef18cef2fcf23ea4f549f30e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Thu, 18 Feb 2021 17:31:18 +0000 Subject: [PATCH 48/93] [HTTP] Apply the same behaviour to all 500 errors (except from `custom` responses) (#85541) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- ...lugin-core-server.kibanaresponsefactory.md | 1 - .../core/server/kibana-plugin-core-server.md | 2 +- ...ibana-plugin-core-server.requesthandler.md | 2 +- .../http/base_path_proxy_server.test.ts | 64 ++++------ src/core/server/http/http_server.mocks.ts | 2 - src/core/server/http/http_server.test.ts | 74 ++++------- .../http/integration_tests/router.test.ts | 6 +- src/core/server/http/router/response.ts | 9 -- src/core/server/http/router/router.ts | 2 + src/core/server/server.api.md | 3 +- .../autocomplete/value_suggestions_route.ts | 14 +- .../errors/handle_es_error.ts | 2 +- .../services/sample_data/routes/install.ts | 6 +- .../vis_type_timelion/server/routes/run.ts | 62 ++++----- .../vis_type_timeseries/server/routes/vis.ts | 18 +-- .../plugins/newsfeed/server/plugin.ts | 2 +- .../plugins/core_plugin_b/server/plugin.ts | 2 +- .../server/routes/tokens/create.ts | 6 +- .../server/routes/catch_error_handler.ts | 2 +- .../server/routes/es_fields/es_fields.test.ts | 6 +- .../register_create_route.ts | 4 +- .../register_delete_route.ts | 2 +- .../register_fetch_route.ts | 2 +- .../auto_follow_pattern/register_get_route.ts | 2 +- .../register_pause_route.ts | 2 +- .../register_resume_route.ts | 2 +- .../register_update_route.ts | 2 +- .../register_permissions_route.ts | 2 +- .../register_stats_route.ts | 2 +- .../follower_index/register_create_route.ts | 2 +- .../follower_index/register_fetch_route.ts | 2 +- .../api/follower_index/register_get_route.ts | 2 +- .../follower_index/register_pause_route.ts | 2 +- .../follower_index/register_resume_route.ts | 2 +- .../follower_index/register_unfollow_route.ts | 2 +- .../follower_index/register_update_route.ts | 2 +- .../enterprise_search/telemetry.test.ts | 29 ++--- .../routes/enterprise_search/telemetry.ts | 27 ++-- x-pack/plugins/graph/server/routes/explore.ts | 6 +- .../routes/api/component_templates/create.ts | 2 +- .../routes/api/component_templates/get.ts | 4 +- .../api/component_templates/privileges.ts | 2 +- .../routes/api/component_templates/update.ts | 2 +- .../api/indices/register_clear_cache_route.ts | 2 +- .../api/indices/register_close_route.ts | 2 +- .../api/indices/register_delete_route.ts | 2 +- .../api/indices/register_flush_route.ts | 2 +- .../api/indices/register_forcemerge_route.ts | 2 +- .../api/indices/register_freeze_route.ts | 2 +- .../routes/api/indices/register_list_route.ts | 2 +- .../routes/api/indices/register_open_route.ts | 2 +- .../api/indices/register_refresh_route.ts | 2 +- .../api/indices/register_reload_route.ts | 2 +- .../api/indices/register_unfreeze_route.ts | 2 +- .../api/mapping/register_mapping_route.ts | 2 +- .../api/settings/register_load_route.ts | 2 +- .../api/settings/register_update_route.ts | 2 +- .../routes/api/stats/register_stats_route.ts | 2 +- .../api/templates/register_create_route.ts | 2 +- .../api/templates/register_get_routes.ts | 2 +- .../api/templates/register_simulate_route.ts | 2 +- .../api/templates/register_update_route.ts | 2 +- .../server/routes/inventory_metadata/index.ts | 48 +++---- .../routes/log_analysis/validation/indices.ts | 82 ++++++------ .../server/routes/log_entries/highlights.ts | 120 +++++++++--------- .../server/routes/log_entries/summary.ts | 52 ++++---- .../routes/log_entries/summary_highlights.ts | 59 ++++----- .../infra/server/routes/metadata/index.ts | 104 +++++++-------- .../infra/server/routes/metrics_api/index.ts | 24 ++-- .../server/routes/metrics_explorer/index.ts | 78 ++++++------ .../infra/server/routes/node_details/index.ts | 54 ++++---- .../infra/server/routes/overview/index.ts | 108 ++++++++-------- .../infra/server/routes/process_list/index.ts | 48 +++---- .../infra/server/routes/snapshot/index.ts | 34 ++--- .../infra/server/routes/source/index.ts | 76 +++++------ .../server/routes/api/create.ts | 2 +- .../server/routes/api/documents.ts | 2 +- .../ingest_pipelines/server/routes/api/get.ts | 4 +- .../server/routes/api/privileges.ts | 34 +++-- .../server/routes/api/simulate.ts | 2 +- .../server/routes/api/update.ts | 2 +- .../lens/server/routes/existing_fields.ts | 6 +- .../plugins/lens/server/routes/field_stats.ts | 7 +- .../plugins/lens/server/routes/telemetry.ts | 7 +- .../api/license/register_license_route.ts | 20 ++- .../api/license/register_permissions_route.ts | 10 +- .../api/license/register_start_basic_route.ts | 18 +-- .../license/register_start_trial_routes.ts | 16 +-- .../logstash/server/routes/cluster/load.ts | 2 +- x-pack/plugins/monitoring/server/plugin.ts | 2 +- .../server/routes/api/add_route.ts | 2 +- .../server/routes/api/delete_route.ts | 4 +- .../server/routes/api/get_route.test.ts | 12 +- .../server/routes/api/get_route.ts | 2 +- .../server/routes/api/update_route.ts | 2 +- .../routes/api/indices/register_get_route.ts | 2 +- .../register_validate_index_pattern_route.ts | 2 +- .../routes/api/jobs/register_create_route.ts | 2 +- .../routes/api/jobs/register_delete_route.ts | 2 +- .../routes/api/jobs/register_get_route.ts | 2 +- .../routes/api/jobs/register_start_route.ts | 2 +- .../routes/api/jobs/register_stop_route.ts | 2 +- .../api/search/register_search_route.ts | 2 +- .../authentication_service.test.ts | 13 +- .../authentication/authentication_service.ts | 8 +- .../routes/authentication/common.test.ts | 16 +-- .../server/routes/authentication/common.ts | 40 +++--- .../server/routes/authentication/saml.test.ts | 6 +- .../server/routes/authentication/saml.ts | 35 +++-- .../routes/session_management/info.test.ts | 6 +- .../server/routes/session_management/info.ts | 35 +++-- .../server/routes/views/access_agreement.ts | 21 ++- .../routes/artifacts/download_artifact.ts | 4 +- .../endpoint/routes/metadata/handlers.ts | 89 ++++++------- .../endpoint/routes/policy/handlers.test.ts | 12 +- .../server/endpoint/routes/policy/handlers.ts | 73 +++++------ .../server/endpoint/routes/policy/index.ts | 2 +- .../server/endpoint/routes/resolver.ts | 9 +- .../server/endpoint/routes/resolver/events.ts | 32 ++--- .../endpoint/routes/resolver/tree/handler.ts | 23 ++-- .../routes/trusted_apps/handlers.test.ts | 68 +++++----- .../endpoint/routes/trusted_apps/handlers.ts | 61 ++------- .../endpoint/routes/trusted_apps/index.ts | 14 +- .../security_solution/server/plugin.ts | 4 +- .../snapshot_restore/server/routes/api/app.ts | 2 +- .../server/routes/api/policy.test.ts | 21 +-- .../server/routes/api/policy.ts | 14 +- .../server/routes/api/repositories.test.ts | 16 +-- .../server/routes/api/repositories.ts | 16 +-- .../server/routes/api/restore.test.ts | 6 +- .../server/routes/api/restore.ts | 4 +- .../server/routes/api/snapshots.test.ts | 6 +- .../server/routes/api/snapshots.ts | 6 +- .../task_manager/server/routes/health.test.ts | 6 +- .../server/routes/cluster_checkup.test.ts | 12 +- .../server/routes/cluster_checkup.ts | 2 +- .../server/routes/deprecation_logging.test.ts | 24 ++-- .../server/routes/deprecation_logging.ts | 20 +-- .../routes/reindex_indices/reindex_indices.ts | 4 +- .../server/routes/telemetry.test.ts | 60 ++++----- .../server/routes/telemetry.ts | 42 +++--- .../server/rest_api/create_route_with_auth.ts | 2 +- .../server/rest_api/uptime_route_wrapper.ts | 42 +++--- .../routes/api/indices/register_get_route.ts | 2 +- .../routes/api/register_list_fields_route.ts | 2 +- .../routes/api/register_load_history_route.ts | 2 +- .../api/settings/register_load_route.ts | 2 +- .../action/register_acknowledge_route.ts | 2 +- .../api/watch/register_activate_route.ts | 2 +- .../api/watch/register_deactivate_route.ts | 2 +- .../routes/api/watch/register_delete_route.ts | 2 +- .../api/watch/register_execute_route.ts | 2 +- .../api/watch/register_history_route.ts | 2 +- .../routes/api/watch/register_load_route.ts | 2 +- .../routes/api/watch/register_save_route.ts | 4 +- .../api/watch/register_visualize_route.ts | 2 +- .../api/watches/register_delete_route.ts | 8 +- .../routes/api/watches/register_list_route.ts | 2 +- .../xpack_legacy/server/routes/settings.ts | 2 +- .../fixtures/plugins/aad/server/plugin.ts | 28 ++-- .../fixtures/plugins/alerts/server/routes.ts | 4 +- .../sample_task_plugin/server/init_routes.ts | 20 ++- 162 files changed, 1024 insertions(+), 1432 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md b/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md index d7eafdce017e43..551cbe3c937504 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md @@ -19,7 +19,6 @@ kibanaResponseFactory: { forbidden: (options?: ErrorHttpResponseOptions) => KibanaResponse; notFound: (options?: ErrorHttpResponseOptions) => KibanaResponse; conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; - internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; customError: (options: CustomHttpResponseOptions) => KibanaResponse; redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 3ec63840a67cba..d14e41cfb56ecd 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -283,7 +283,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginOpaqueId](./kibana-plugin-core-server.pluginopaqueid.md) | | | [PublicUiSettingsParams](./kibana-plugin-core-server.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) exposed to the client-side. | | [RedirectResponseOptions](./kibana-plugin-core-server.redirectresponseoptions.md) | HTTP response parameters for redirection response | -| [RequestHandler](./kibana-plugin-core-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) functions. | +| [RequestHandler](./kibana-plugin-core-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) functions. If anything else is returned, or an error is thrown, the HTTP service will automatically log the error and respond 500 - Internal Server Error. | | [RequestHandlerContextContainer](./kibana-plugin-core-server.requesthandlercontextcontainer.md) | An object that handles registration of http request context providers. | | [RequestHandlerContextProvider](./kibana-plugin-core-server.requesthandlercontextprovider.md) | Context provider for request handler. Extends request context object with provided functionality or data. | | [RequestHandlerWrapper](./kibana-plugin-core-server.requesthandlerwrapper.md) | Type-safe wrapper for [RequestHandler](./kibana-plugin-core-server.requesthandler.md) function. | diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandler.md b/docs/development/core/server/kibana-plugin-core-server.requesthandler.md index 0032e52a0e9060..d32ac4d80c337b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandler.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandler.md @@ -4,7 +4,7 @@ ## RequestHandler type -A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) functions. +A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) functions. If anything else is returned, or an error is thrown, the HTTP service will automatically log the error and respond `500 - Internal Server Error`. Signature: diff --git a/src/core/server/http/base_path_proxy_server.test.ts b/src/core/server/http/base_path_proxy_server.test.ts index 8f3a63058a8ae0..80c03a2af9031b 100644 --- a/src/core/server/http/base_path_proxy_server.test.ts +++ b/src/core/server/http/base_path_proxy_server.test.ts @@ -705,12 +705,8 @@ describe('BasePathProxyServer', () => { options: { body: { output: 'stream' } }, }, (_, req, res) => { - try { - expect(req.body).toBeInstanceOf(Readable); - return res.ok({ body: req.route.options.body }); - } catch (err) { - return res.internalError({ body: err.message }); - } + expect(req.body).toBeInstanceOf(Readable); + return res.ok({ body: req.route.options.body }); } ); registerRouter(router); @@ -740,15 +736,11 @@ describe('BasePathProxyServer', () => { }, }, (_, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -779,15 +771,11 @@ describe('BasePathProxyServer', () => { }, }, (context, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -815,15 +803,11 @@ describe('BasePathProxyServer', () => { }, }, (_, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -851,15 +835,11 @@ describe('BasePathProxyServer', () => { }, }, (_, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index 71452cce246dfa..52dab28accb33b 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -151,7 +151,6 @@ const createResponseFactoryMock = (): jest.Mocked => ({ forbidden: jest.fn(), notFound: jest.fn(), conflict: jest.fn(), - internalError: jest.fn(), customError: jest.fn(), }); @@ -162,7 +161,6 @@ const createLifecycleResponseFactoryMock = (): jest.Mocked { options: { body: { parse: false } }, }, (context, req, res) => { - try { - expect(req.body).toBeInstanceOf(Buffer); - expect(req.body.toString()).toBe(JSON.stringify({ test: 1 })); - return res.ok({ body: req.route.options.body }); - } catch (err) { - return res.internalError({ body: err.message }); - } + expect(req.body).toBeInstanceOf(Buffer); + expect(req.body.toString()).toBe(JSON.stringify({ test: 1 })); + return res.ok({ body: req.route.options.body }); } ); registerRouter(router); @@ -1053,15 +1049,11 @@ describe('timeout options', () => { }, }, (context, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -1091,15 +1083,11 @@ describe('timeout options', () => { }, }, (context, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -1128,15 +1116,11 @@ describe('timeout options', () => { }, }, (context, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -1165,15 +1149,11 @@ describe('timeout options', () => { }, }, (context, req, res) => { - try { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } catch (err) { - return res.internalError({ body: err.message }); - } + return res.ok({ + body: { + timeout: req.route.options.timeout, + }, + }); } ); registerRouter(router); @@ -1294,12 +1274,8 @@ test('should return a stream in the body', async () => { options: { body: { output: 'stream' } }, }, (context, req, res) => { - try { - expect(req.body).toBeInstanceOf(Readable); - return res.ok({ body: req.route.options.body }); - } catch (err) { - return res.internalError({ body: err.message }); - } + expect(req.body).toBeInstanceOf(Readable); + return res.ok({ body: req.route.options.body }); } ); registerRouter(router); diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index c9c4410171b34a..6c54067435405a 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -1672,7 +1672,11 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(500); - expect(result.body.message).toBe('reason'); + expect(result.body).toEqual({ + error: 'Internal Server Error', + message: 'reason', + statusCode: 500, + }); expect(loggingSystemMock.collect(logger).error).toHaveLength(0); }); diff --git a/src/core/server/http/router/response.ts b/src/core/server/http/router/response.ts index f4e09fa1cc08ea..e2babf719f67e0 100644 --- a/src/core/server/http/router/response.ts +++ b/src/core/server/http/router/response.ts @@ -177,15 +177,6 @@ const errorResponseFactory = { conflict: (options: ErrorHttpResponseOptions = {}) => new KibanaResponse(409, options.body || 'Conflict', options), - // Server error - /** - * The server encountered an unexpected condition that prevented it from fulfilling the request. - * Status code: `500`. - * @param options - {@link HttpResponseOptions} configures HTTP response headers, error message and other error details to pass to the client - */ - internalError: (options: ErrorHttpResponseOptions = {}) => - new KibanaResponse(500, options.body || 'Internal Error', options), - /** * Creates an error response with defined status code and payload. * @param options - {@link CustomHttpResponseOptions} configures HTTP response headers, error message and other error details to pass to the client diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index acce209751e325..85eab7c0892e88 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -314,6 +314,8 @@ type RequestHandlerEnhanced = WithoutHeadAr /** * A function executed when route path matched requested resource path. * Request handler is expected to return a result of one of {@link KibanaResponseFactory} functions. + * If anything else is returned, or an error is thrown, the HTTP service will automatically log the error + * and respond `500 - Internal Server Error`. * @param context {@link RequestHandlerContext} - the core context exposed for this request. * @param request {@link KibanaRequest} - object containing information about requested resource, * such as path, method, headers, parameters, query, body, etc. diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 377cd2bc2068a9..2177da84b2b53d 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1276,7 +1276,6 @@ export const kibanaResponseFactory: { forbidden: (options?: ErrorHttpResponseOptions) => KibanaResponse; notFound: (options?: ErrorHttpResponseOptions) => KibanaResponse; conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; - internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; customError: (options: CustomHttpResponseOptions) => KibanaResponse; redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; @@ -3197,7 +3196,7 @@ export const validBodyOutput: readonly ["data", "stream"]; // Warnings were encountered during analysis: // -// src/core/server/http/router/response.ts:306:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts +// src/core/server/http/router/response.ts:297:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:280:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:280:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:283:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index 8a633d8d827f4e..489a23eb83897c 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -57,17 +57,13 @@ export function registerValueSuggestionsRoute( const field = indexPattern && getFieldByName(fieldName, indexPattern); const body = await getBody(autocompleteSearchOptions, field || fieldName, query, filters); - try { - const result = await client.callAsCurrentUser('search', { index, body }, { signal }); + const result = await client.callAsCurrentUser('search', { index, body }, { signal }); - const buckets: any[] = - get(result, 'aggregations.suggestions.buckets') || - get(result, 'aggregations.nestedSuggestions.suggestions.buckets'); + const buckets: any[] = + get(result, 'aggregations.suggestions.buckets') || + get(result, 'aggregations.nestedSuggestions.suggestions.buckets'); - return response.ok({ body: map(buckets || [], 'key') }); - } catch (error) { - return response.internalError({ body: error }); - } + return response.ok({ body: map(buckets || [], 'key') }); } ); } diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts index b645b62c863d5d..4a45cff0b96049 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts @@ -38,5 +38,5 @@ export const handleEsError = ({ }); } // Case: default - return response.internalError({ body: error }); + throw error; }; diff --git a/src/plugins/home/server/services/sample_data/routes/install.ts b/src/plugins/home/server/services/sample_data/routes/install.ts index 7c00a46602e26e..a20c3e350222f3 100644 --- a/src/plugins/home/server/services/sample_data/routes/install.ts +++ b/src/plugins/home/server/services/sample_data/routes/install.ts @@ -136,8 +136,7 @@ export function createInstallRoute( (counts as any)[index] = count; } catch (err) { const errMsg = `sample_data install errors while loading data. Error: ${err}`; - logger.warn(errMsg); - return res.internalError({ body: errMsg }); + throw new Error(errMsg); } } @@ -157,8 +156,7 @@ export function createInstallRoute( ); } catch (err) { const errMsg = `bulkCreate failed, error: ${err.message}`; - logger.warn(errMsg); - return res.internalError({ body: errMsg }); + throw new Error(errMsg); } const errors = createResults.saved_objects.filter((savedObjectCreateResult) => { return Boolean(savedObjectCreateResult.error); diff --git a/src/plugins/vis_type_timelion/server/routes/run.ts b/src/plugins/vis_type_timelion/server/routes/run.ts index bae25d6f918e37..b3ab3c61c15d89 100644 --- a/src/plugins/vis_type_timelion/server/routes/run.ts +++ b/src/plugins/vis_type_timelion/server/routes/run.ts @@ -77,46 +77,32 @@ export function runRoute( }, }, router.handleLegacyErrors(async (context, request, response) => { - try { - const [, { data }] = await core.getStartServices(); - const uiSettings = await context.core.uiSettings.client.getAll(); - const indexPatternsService = await data.indexPatterns.indexPatternsServiceFactory( - context.core.savedObjects.client, - context.core.elasticsearch.client.asCurrentUser - ); + const [, { data }] = await core.getStartServices(); + const uiSettings = await context.core.uiSettings.client.getAll(); + const indexPatternsService = await data.indexPatterns.indexPatternsServiceFactory( + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser + ); - const tlConfig = getTlConfig({ - context, - request, - settings: _.defaults(uiSettings, timelionDefaults), // Just in case they delete some setting. - getFunction, - getIndexPatternsService: () => indexPatternsService, - getStartServices: core.getStartServices, - allowedGraphiteUrls: configManager.getGraphiteUrls(), - esShardTimeout: configManager.getEsShardTimeout(), - }); - const chainRunner = chainRunnerFn(tlConfig); - const sheet = await Bluebird.all(chainRunner.processRequest(request.body)); + const tlConfig = getTlConfig({ + context, + request, + settings: _.defaults(uiSettings, timelionDefaults), // Just in case they delete some setting. + getFunction, + getIndexPatternsService: () => indexPatternsService, + getStartServices: core.getStartServices, + allowedGraphiteUrls: configManager.getGraphiteUrls(), + esShardTimeout: configManager.getEsShardTimeout(), + }); + const chainRunner = chainRunnerFn(tlConfig); + const sheet = await Bluebird.all(chainRunner.processRequest(request.body)); - return response.ok({ - body: { - sheet, - stats: chainRunner.getStats(), - }, - }); - } catch (err) { - logger.error(`${err.toString()}: ${err.stack}`); - // TODO Maybe we should just replace everywhere we throw with Boom? Probably. - if (err.isBoom) { - throw err; - } else { - return response.internalError({ - body: { - message: err.toString(), - }, - }); - } - } + return response.ok({ + body: { + sheet, + stats: chainRunner.getStats(), + }, + }); }) ); } diff --git a/src/plugins/vis_type_timeseries/server/routes/vis.ts b/src/plugins/vis_type_timeseries/server/routes/vis.ts index 7c314228dad246..15890011d75bb9 100644 --- a/src/plugins/vis_type_timeseries/server/routes/vis.ts +++ b/src/plugins/vis_type_timeseries/server/routes/vis.ts @@ -42,18 +42,12 @@ export const visDataRoutes = (router: VisTypeTimeseriesRouter, framework: Framew ); } - try { - const results = await getVisData( - requestContext, - request as KibanaRequest<{}, {}, GetVisDataOptions>, - framework - ); - return response.ok({ body: results }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + const results = await getVisData( + requestContext, + request as KibanaRequest<{}, {}, GetVisDataOptions>, + framework + ); + return response.ok({ body: results }); } ); }; diff --git a/test/common/fixtures/plugins/newsfeed/server/plugin.ts b/test/common/fixtures/plugins/newsfeed/server/plugin.ts index 732bc6c4732436..49ffa464efac92 100644 --- a/test/common/fixtures/plugins/newsfeed/server/plugin.ts +++ b/test/common/fixtures/plugins/newsfeed/server/plugin.ts @@ -34,7 +34,7 @@ export class NewsFeedSimulatorPlugin implements Plugin { options: { authRequired: false }, }, (context, req, res) => { - return res.internalError({ body: new Error('Internal server error') }); + throw new Error('Internal server error'); } ); } diff --git a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts index 47644256b05e7c..ae7abdaebba101 100644 --- a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts @@ -18,7 +18,7 @@ export class CorePluginBPlugin implements Plugin { public setup(core: CoreSetup, deps: {}) { const router = core.http.createRouter(); router.get({ path: '/core_plugin_b', validate: false }, async (context, req, res) => { - if (!context.pluginA) return res.internalError({ body: 'pluginA is disabled' }); + if (!context.pluginA) throw new Error('pluginA is disabled'); const response = await context.pluginA.ping(); return res.ok({ body: `Pong via plugin A: ${response}` }); }); diff --git a/x-pack/plugins/beats_management/server/routes/tokens/create.ts b/x-pack/plugins/beats_management/server/routes/tokens/create.ts index d61e96900e04b8..c44f9c2dd4e7dc 100644 --- a/x-pack/plugins/beats_management/server/routes/tokens/create.ts +++ b/x-pack/plugins/beats_management/server/routes/tokens/create.ts @@ -50,11 +50,7 @@ export const registerCreateTokenRoute = (router: BeatsManagementRouter) => { }); } catch (err) { beatsManagement.framework.log(err.message); - return response.internalError({ - body: { - message: 'An error occurred, please check your Kibana logs', - }, - }); + throw new Error('An error occurred, please check your Kibana logs'); } } ) diff --git a/x-pack/plugins/canvas/server/routes/catch_error_handler.ts b/x-pack/plugins/canvas/server/routes/catch_error_handler.ts index 2abdda1932535a..b1fe4bc798f6bc 100644 --- a/x-pack/plugins/canvas/server/routes/catch_error_handler.ts +++ b/x-pack/plugins/canvas/server/routes/catch_error_handler.ts @@ -20,7 +20,7 @@ export const catchErrorHandler: ( statusCode: error.output.statusCode, }); } - return response.internalError({ body: error }); + throw error; } }; }; diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts index 618625cd6cdd43..1e95ee809e7679 100644 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts @@ -147,8 +147,8 @@ describe('Retrieve ES Fields', () => { callAsCurrentUserMock.mockRejectedValueOnce(new Error('Index not found')); - const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); - - expect(response.status).toBe(500); + await expect( + routeHandler(mockRouteContext, request, kibanaResponseFactory) + ).rejects.toThrowError('Index not found'); }); }); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts index 10cb83877f57f3..608e369828de6f 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts @@ -55,7 +55,7 @@ export const registerCreateRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } } @@ -71,7 +71,7 @@ export const registerCreateRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts index bc18d5c4b10f81..868e847bd6bf48 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts @@ -41,7 +41,7 @@ export const registerDeleteRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts index 0e6ebc5270986f..632fdb03dd5887 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts @@ -37,7 +37,7 @@ export const registerFetchRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts index cd1cb227a3eba7..3529fe313dbb16 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts @@ -48,7 +48,7 @@ export const registerGetRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts index 3bdcd4824a9d45..a9aa94cdf4f299 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts @@ -40,7 +40,7 @@ export const registerPauseRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts index 594ef79eaedc96..1c6396d0b35022 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts @@ -40,7 +40,7 @@ export const registerResumeRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts index e4d0b7d9d65149..a3e7c3544ca370 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts @@ -54,7 +54,7 @@ export const registerUpdateRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts index ac956a45b87026..130adb2e0b9892 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts @@ -64,7 +64,7 @@ export const registerPermissionsRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts index f37dafca86c985..6636f6b1c5accc 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts @@ -36,7 +36,7 @@ export const registerStatsRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts index b254606af8a867..f44e5a749baad7 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts @@ -59,7 +59,7 @@ export const registerCreateRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts index ffb560ebd5f2bf..c72706cf5d10dc 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts @@ -56,7 +56,7 @@ export const registerFetchRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts index 36feb2e69a3f5c..cdcfff97e645db 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts @@ -72,7 +72,7 @@ export const registerGetRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts index dd76114d30215a..2e4e71278df0e9 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts @@ -38,7 +38,7 @@ export const registerPauseRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts index 22206c70fdbbf8..34f204f3b64b96 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts @@ -38,7 +38,7 @@ export const registerResumeRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts index 05bf99e2d8c67f..848408e14662fd 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts @@ -39,7 +39,7 @@ export const registerUnfollowRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + return response.customError({ statusCode: 500, body: err }); }; await Promise.all( diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts index 0b61db42cc7635..933d13a0a223c0 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts @@ -87,7 +87,7 @@ export const registerUpdateRoute = ({ return response.customError(formatEsError(err)); } // Case: default - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts index 62f68748fcea19..53ddc21cba3994 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts @@ -7,7 +7,7 @@ import { MockRouter, mockLogger, mockDependencies } from '../../__mocks__'; -import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks'; +import { savedObjectsServiceMock } from 'src/core/server/mocks'; jest.mock('../../collectors/lib/telemetry', () => ({ incrementUICounter: jest.fn(), @@ -84,17 +84,17 @@ describe('Enterprise Search Telemetry API', () => { it('throws an error when incrementing fails', async () => { (incrementUICounter as jest.Mock).mockImplementation(jest.fn(() => Promise.reject('Failed'))); - await mockRouter.callRoute({ - body: { - product: 'enterprise_search', - action: 'error', - metric: 'error', - }, - }); + await expect( + mockRouter.callRoute({ + body: { + product: 'enterprise_search', + action: 'error', + metric: 'error', + }, + }) + ).rejects.toEqual('Failed'); expect(incrementUICounter).toHaveBeenCalled(); - expect(mockLogger.error).toHaveBeenCalled(); - expect(mockRouter.response.internalError).toHaveBeenCalled(); }); it('throws an error if the Saved Objects service is unavailable', async () => { @@ -104,16 +104,9 @@ describe('Enterprise Search Telemetry API', () => { getSavedObjectsService: null, log: mockLogger, } as any); - await mockRouter.callRoute({}); + await expect(mockRouter.callRoute({})).rejects.toThrow(); expect(incrementUICounter).not.toHaveBeenCalled(); - expect(mockLogger.error).toHaveBeenCalled(); - expect(mockRouter.response.internalError).toHaveBeenCalled(); - expect(loggingSystemMock.collect(mockLogger).error[0][0]).toEqual( - expect.stringContaining( - 'Enterprise Search UI telemetry error: Error: Could not find Saved Objects service' - ) - ); }); describe('validates', () => { diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts index 90afba414c0447..e15be8fcd0d8b8 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts @@ -20,7 +20,7 @@ const productToTelemetryMap = { workplace_search: WS_TELEMETRY_NAME, }; -export function registerTelemetryRoute({ router, getSavedObjectsService, log }: RouteDependencies) { +export function registerTelemetryRoute({ router, getSavedObjectsService }: RouteDependencies) { router.put( { path: '/api/enterprise_search/stats', @@ -43,23 +43,16 @@ export function registerTelemetryRoute({ router, getSavedObjectsService, log }: async (ctx, request, response) => { const { product, action, metric } = request.body; - try { - if (!getSavedObjectsService) throw new Error('Could not find Saved Objects service'); + if (!getSavedObjectsService) throw new Error('Could not find Saved Objects service'); - return response.ok({ - body: await incrementUICounter({ - id: productToTelemetryMap[product], - savedObjects: getSavedObjectsService(), - uiAction: `ui_${action}`, - metric, - }), - }); - } catch (e) { - log.error( - `Enterprise Search UI telemetry error: ${e instanceof Error ? e.stack : e.toString()}` - ); - return response.internalError({ body: 'Enterprise Search UI telemetry failed' }); - } + return response.ok({ + body: await incrementUICounter({ + id: productToTelemetryMap[product], + savedObjects: getSavedObjectsService(), + uiAction: `ui_${action}`, + metric, + }), + }); } ); } diff --git a/x-pack/plugins/graph/server/routes/explore.ts b/x-pack/plugins/graph/server/routes/explore.ts index 95be71812d06c8..9a9a267c40f32a 100644 --- a/x-pack/plugins/graph/server/routes/explore.ts +++ b/x-pack/plugins/graph/server/routes/explore.ts @@ -76,11 +76,7 @@ export function registerExploreRoute({ } } - return response.internalError({ - body: { - message: error.message, - }, - }); + throw error; } } ) diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts index d4d16933c518ce..a6c0592e035e79 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts @@ -71,7 +71,7 @@ export const registerCreateRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts index 28773b6233b552..552aa5a9a2888c 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts @@ -54,7 +54,7 @@ export function registerGetAllRoute({ router, license, lib: { isEsError } }: Rou }); } - return res.internalError({ body: error }); + throw error; } }) ); @@ -94,7 +94,7 @@ export function registerGetAllRoute({ router, license, lib: { isEsError } }: Rou }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts index b7957737d3aae8..1ed6555eb38067 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts @@ -64,7 +64,7 @@ export const registerPrivilegesRoute = ({ license, router, config }: RouteDepend return res.ok({ body: privilegesResult }); } catch (e) { - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts index b0113e8566ae2e..42b53ab6ee25b3 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts @@ -55,7 +55,7 @@ export const registerUpdateRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts index cbb7add344a60e..2f5da4b1d8957a 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts @@ -38,7 +38,7 @@ export function registerClearCacheRoute({ router, license, lib }: RouteDependenc }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts index 5a0d692c743185..1a0babfc3a5b1f 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts @@ -38,7 +38,7 @@ export function registerCloseRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts index dfe571f352296f..9a022d4595d1c0 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts @@ -38,7 +38,7 @@ export function registerDeleteRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts index 8faba8a5d54cd0..b064f3520004a0 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts @@ -38,7 +38,7 @@ export function registerFlushRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts index d8a777196e7ef5..1c14f660b98c67 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts @@ -45,7 +45,7 @@ export function registerForcemergeRoute({ router, license, lib }: RouteDependenc }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts index 4d18650d928265..b669d78f2ba597 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts @@ -40,7 +40,7 @@ export function registerFreezeRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts index b940253635ad0e..0b253b9fe66c97 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts @@ -27,7 +27,7 @@ export function registerListRoute({ router, license, indexDataEnricher, lib }: R }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts index 322eac45d7bd1f..a35ddfcf4d91b2 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts @@ -38,7 +38,7 @@ export function registerOpenRoute({ router, license, lib }: RouteDependencies) { }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts index a070208d30d4cd..f69d2d90a5b8fc 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts @@ -38,7 +38,7 @@ export function registerRefreshRoute({ router, license, lib }: RouteDependencies }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts index b6b8d741c1202c..04b7d760fc1d62 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts @@ -43,7 +43,7 @@ export function registerReloadRoute({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts index 55281951790331..3cda4d6b5f1682 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts @@ -35,7 +35,7 @@ export function registerUnfreezeRoute({ router, license, lib }: RouteDependencie }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts b/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts index 5cc0c92969ab09..f0b62bacdee426 100644 --- a/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts @@ -46,7 +46,7 @@ export function registerMappingRoute({ router, license, lib }: RouteDependencies }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts b/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts index 6d355ced5e9939..7a661a9e9e4f49 100644 --- a/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts @@ -48,7 +48,7 @@ export function registerLoadRoute({ router, license, lib }: RouteDependencies) { }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts b/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts index 1216a9c74e48c4..4c153d6293a79f 100644 --- a/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts @@ -46,7 +46,7 @@ export function registerUpdateRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts b/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts index 45bf114805a787..f8385711b55fe2 100644 --- a/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts @@ -47,7 +47,7 @@ export function registerStatsRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts index 81286b69f2ded8..97e3c380e13ecd 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts @@ -62,7 +62,7 @@ export function registerCreateRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts index 88aa8d3a793501..006532cfd4dbe1 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts @@ -104,7 +104,7 @@ export function registerGetOneRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts index d24837fa01cd50..f4554bd2fb1fa6 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts @@ -40,7 +40,7 @@ export function registerSimulateRoute({ router, license, lib }: RouteDependencie }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts index bf88d63572a823..f0070408768cbf 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts @@ -56,7 +56,7 @@ export function registerUpdateRoute({ router, license, lib }: RouteDependencies) }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts b/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts index 7147f224d09d57..70d29773f76c5d 100644 --- a/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts +++ b/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts @@ -33,33 +33,27 @@ export const initInventoryMetaRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const { sourceId, nodeType, currentTime } = pipe( - InventoryMetaRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - - const { configuration } = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - sourceId - ); - - const awsMetadata = await getCloudMetadata( - framework, - requestContext, - configuration, - nodeType, - currentTime - ); - - return response.ok({ - body: InventoryMetaResponseRT.encode(awsMetadata), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + const { sourceId, nodeType, currentTime } = pipe( + InventoryMetaRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + + const { configuration } = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); + + const awsMetadata = await getCloudMetadata( + framework, + requestContext, + configuration, + nodeType, + currentTime + ); + + return response.ok({ + body: InventoryMetaResponseRT.encode(awsMetadata), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/log_analysis/validation/indices.ts b/x-pack/plugins/infra/server/routes/log_analysis/validation/indices.ts index c49d6034c95c41..463ac77891263c 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/validation/indices.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/validation/indices.ts @@ -31,65 +31,59 @@ export const initValidateLogAnalysisIndicesRoute = ({ framework }: InfraBackendL validate: { body: escapeHatch }, }, async (requestContext, request, response) => { - try { - const payload = pipe( - validationIndicesRequestPayloadRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const payload = pipe( + validationIndicesRequestPayloadRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const { fields, indices } = payload.data; - const errors: ValidationIndicesError[] = []; + const { fields, indices } = payload.data; + const errors: ValidationIndicesError[] = []; - // Query each pattern individually, to map correctly the errors - await Promise.all( - indices.map(async (index) => { - const fieldCaps = await framework.callWithRequest(requestContext, 'fieldCaps', { - allow_no_indices: true, - fields: fields.map((field) => field.name), - ignore_unavailable: true, + // Query each pattern individually, to map correctly the errors + await Promise.all( + indices.map(async (index) => { + const fieldCaps = await framework.callWithRequest(requestContext, 'fieldCaps', { + allow_no_indices: true, + fields: fields.map((field) => field.name), + ignore_unavailable: true, + index, + }); + + if (fieldCaps.indices.length === 0) { + errors.push({ + error: 'INDEX_NOT_FOUND', index, }); + return; + } + + fields.forEach(({ name: fieldName, validTypes }) => { + const fieldMetadata = fieldCaps.fields[fieldName]; - if (fieldCaps.indices.length === 0) { + if (fieldMetadata === undefined) { errors.push({ - error: 'INDEX_NOT_FOUND', + error: 'FIELD_NOT_FOUND', index, + field: fieldName, }); - return; - } - - fields.forEach(({ name: fieldName, validTypes }) => { - const fieldMetadata = fieldCaps.fields[fieldName]; + } else { + const fieldTypes = Object.keys(fieldMetadata); - if (fieldMetadata === undefined) { + if (!fieldTypes.every((fieldType) => validTypes.includes(fieldType))) { errors.push({ - error: 'FIELD_NOT_FOUND', + error: `FIELD_NOT_VALID`, index, field: fieldName, }); - } else { - const fieldTypes = Object.keys(fieldMetadata); - - if (!fieldTypes.every((fieldType) => validTypes.includes(fieldType))) { - errors.push({ - error: `FIELD_NOT_VALID`, - index, - field: fieldName, - }); - } } - }); - }) - ); + } + }); + }) + ); - return response.ok({ - body: validationIndicesResponsePayloadRT.encode({ data: { errors } }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: validationIndicesResponsePayloadRT.encode({ data: { errors } }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts b/x-pack/plugins/infra/server/routes/log_entries/highlights.ts index c72590ca01a5ef..bb7c615358c0e1 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/highlights.ts @@ -33,75 +33,69 @@ export const initLogEntriesHighlightsRoute = ({ framework, logEntries }: InfraBa validate: { body: escapeHatch }, }, async (requestContext, request, response) => { - try { - const payload = pipe( - logEntriesHighlightsRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - - const { startTimestamp, endTimestamp, sourceId, query, size, highlightTerms } = payload; + const payload = pipe( + logEntriesHighlightsRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - let entriesPerHighlightTerm; + const { startTimestamp, endTimestamp, sourceId, query, size, highlightTerms } = payload; - if ('center' in payload) { - entriesPerHighlightTerm = await Promise.all( - highlightTerms.map((highlightTerm) => - logEntries.getLogEntriesAround(requestContext, sourceId, { - startTimestamp, - endTimestamp, - query: parseFilterQuery(query), - center: payload.center, - size, - highlightTerm, - }) - ) - ); - } else { - let cursor: LogEntriesParams['cursor']; - if ('before' in payload) { - cursor = { before: payload.before }; - } else if ('after' in payload) { - cursor = { after: payload.after }; - } + let entriesPerHighlightTerm; - entriesPerHighlightTerm = await Promise.all( - highlightTerms.map((highlightTerm) => - logEntries.getLogEntries(requestContext, sourceId, { - startTimestamp, - endTimestamp, - query: parseFilterQuery(query), - cursor, - size, - highlightTerm, - }) - ) - ); + if ('center' in payload) { + entriesPerHighlightTerm = await Promise.all( + highlightTerms.map((highlightTerm) => + logEntries.getLogEntriesAround(requestContext, sourceId, { + startTimestamp, + endTimestamp, + query: parseFilterQuery(query), + center: payload.center, + size, + highlightTerm, + }) + ) + ); + } else { + let cursor: LogEntriesParams['cursor']; + if ('before' in payload) { + cursor = { before: payload.before }; + } else if ('after' in payload) { + cursor = { after: payload.after }; } - return response.ok({ - body: logEntriesHighlightsResponseRT.encode({ - data: entriesPerHighlightTerm.map(({ entries }) => { - if (entries.length > 0) { - return { - entries, - topCursor: entries[0].cursor, - bottomCursor: entries[entries.length - 1].cursor, - }; - } else { - return { - entries, - topCursor: null, - bottomCursor: null, - }; - } - }), - }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); + entriesPerHighlightTerm = await Promise.all( + highlightTerms.map((highlightTerm) => + logEntries.getLogEntries(requestContext, sourceId, { + startTimestamp, + endTimestamp, + query: parseFilterQuery(query), + cursor, + size, + highlightTerm, + }) + ) + ); } + + return response.ok({ + body: logEntriesHighlightsResponseRT.encode({ + data: entriesPerHighlightTerm.map(({ entries }) => { + if (entries.length > 0) { + return { + entries, + topCursor: entries[0].cursor, + bottomCursor: entries[entries.length - 1].cursor, + }; + } else { + return { + entries, + topCursor: null, + bottomCursor: null, + }; + } + }), + }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/log_entries/summary.ts b/x-pack/plugins/infra/server/routes/log_entries/summary.ts index 4849b56b1d579d..3ff0ded8a7c244 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/summary.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/summary.ts @@ -33,38 +33,32 @@ export const initLogEntriesSummaryRoute = ({ framework, logEntries }: InfraBacke validate: { body: escapeHatch }, }, async (requestContext, request, response) => { - try { - const payload = pipe( - logEntriesSummaryRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - const { sourceId, startTimestamp, endTimestamp, bucketSize, query } = payload; + const payload = pipe( + logEntriesSummaryRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + const { sourceId, startTimestamp, endTimestamp, bucketSize, query } = payload; - const buckets = await logEntries.getLogSummaryBucketsBetween( - requestContext, - sourceId, - startTimestamp, - endTimestamp, - bucketSize, - parseFilterQuery(query) - ); + const buckets = await logEntries.getLogSummaryBucketsBetween( + requestContext, + sourceId, + startTimestamp, + endTimestamp, + bucketSize, + parseFilterQuery(query) + ); - UsageCollector.countLogs(); + UsageCollector.countLogs(); - return response.ok({ - body: logEntriesSummaryResponseRT.encode({ - data: { - start: startTimestamp, - end: endTimestamp, - buckets, - }, - }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: logEntriesSummaryResponseRT.encode({ + data: { + start: startTimestamp, + end: endTimestamp, + buckets, + }, + }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts b/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts index 62a9d15c4e68bc..ca219cac41e2bf 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/summary_highlights.ts @@ -35,44 +35,31 @@ export const initLogEntriesSummaryHighlightsRoute = ({ validate: { body: escapeHatch }, }, async (requestContext, request, response) => { - try { - const payload = pipe( - logEntriesSummaryHighlightsRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - const { - sourceId, - startTimestamp, - endTimestamp, - bucketSize, - query, - highlightTerms, - } = payload; + const payload = pipe( + logEntriesSummaryHighlightsRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + const { sourceId, startTimestamp, endTimestamp, bucketSize, query, highlightTerms } = payload; - const bucketsPerHighlightTerm = await logEntries.getLogSummaryHighlightBucketsBetween( - requestContext, - sourceId, - startTimestamp, - endTimestamp, - bucketSize, - highlightTerms, - parseFilterQuery(query) - ); + const bucketsPerHighlightTerm = await logEntries.getLogSummaryHighlightBucketsBetween( + requestContext, + sourceId, + startTimestamp, + endTimestamp, + bucketSize, + highlightTerms, + parseFilterQuery(query) + ); - return response.ok({ - body: logEntriesSummaryHighlightsResponseRT.encode({ - data: bucketsPerHighlightTerm.map((buckets) => ({ - start: startTimestamp, - end: endTimestamp, - buckets, - })), - }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: logEntriesSummaryHighlightsResponseRT.encode({ + data: bucketsPerHighlightTerm.map((buckets) => ({ + start: startTimestamp, + end: endTimestamp, + buckets, + })), + }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/metadata/index.ts b/x-pack/plugins/infra/server/routes/metadata/index.ts index b2abe1c35a3ff1..cc8888e9bd09d3 100644 --- a/x-pack/plugins/infra/server/routes/metadata/index.ts +++ b/x-pack/plugins/infra/server/routes/metadata/index.ts @@ -37,65 +37,57 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const { nodeId, nodeType, sourceId, timeRange } = pipe( - InfraMetadataRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const { nodeId, nodeType, sourceId, timeRange } = pipe( + InfraMetadataRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const { configuration } = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - sourceId - ); - const metricsMetadata = await getMetricMetadata( - framework, - requestContext, - configuration, - nodeId, - nodeType, - timeRange - ); - const metricFeatures = pickFeatureName(metricsMetadata.buckets).map( - nameToFeature('metrics') - ); + const { configuration } = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); + const metricsMetadata = await getMetricMetadata( + framework, + requestContext, + configuration, + nodeId, + nodeType, + timeRange + ); + const metricFeatures = pickFeatureName(metricsMetadata.buckets).map(nameToFeature('metrics')); - const info = await getNodeInfo( - framework, - requestContext, - configuration, - nodeId, - nodeType, - timeRange - ); - const cloudInstanceId = get(info, 'cloud.instance.id'); + const info = await getNodeInfo( + framework, + requestContext, + configuration, + nodeId, + nodeType, + timeRange + ); + const cloudInstanceId = get(info, 'cloud.instance.id'); - const cloudMetricsMetadata = cloudInstanceId - ? await getCloudMetricsMetadata( - framework, - requestContext, - configuration, - cloudInstanceId, - timeRange - ) - : { buckets: [] }; - const cloudMetricsFeatures = pickFeatureName(cloudMetricsMetadata.buckets).map( - nameToFeature('metrics') - ); - const id = metricsMetadata.id; - const name = metricsMetadata.name || id; - return response.ok({ - body: InfraMetadataRT.encode({ - id, - name, - features: [...metricFeatures, ...cloudMetricsFeatures], - info, - }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + const cloudMetricsMetadata = cloudInstanceId + ? await getCloudMetricsMetadata( + framework, + requestContext, + configuration, + cloudInstanceId, + timeRange + ) + : { buckets: [] }; + const cloudMetricsFeatures = pickFeatureName(cloudMetricsMetadata.buckets).map( + nameToFeature('metrics') + ); + const id = metricsMetadata.id; + const name = metricsMetadata.name || id; + return response.ok({ + body: InfraMetadataRT.encode({ + id, + name, + features: [...metricFeatures, ...cloudMetricsFeatures], + info, + }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/metrics_api/index.ts b/x-pack/plugins/infra/server/routes/metrics_api/index.ts index 7d616f5b9dfeae..5c0569d6e7a94f 100644 --- a/x-pack/plugins/infra/server/routes/metrics_api/index.ts +++ b/x-pack/plugins/infra/server/routes/metrics_api/index.ts @@ -29,23 +29,17 @@ export const initMetricsAPIRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const options = pipe( - MetricsAPIRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const options = pipe( + MetricsAPIRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const client = createSearchClient(requestContext, framework); - const metricsApiResponse = await query(client, options); + const client = createSearchClient(requestContext, framework); + const metricsApiResponse = await query(client, options); - return response.ok({ - body: MetricsAPIResponseRT.encode(metricsApiResponse), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: MetricsAPIResponseRT.encode(metricsApiResponse), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/index.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/index.ts index b8a48df43bc10f..d61dcfad974947 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/index.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/index.ts @@ -37,55 +37,49 @@ export const initMetricExplorerRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const options = pipe( - metricsExplorerRequestBodyRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const options = pipe( + metricsExplorerRequestBodyRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const client = createSearchClient(requestContext, framework); - const interval = await findIntervalForMetrics(client, options); + const client = createSearchClient(requestContext, framework); + const interval = await findIntervalForMetrics(client, options); - const optionsWithInterval = options.forceInterval - ? options - : { - ...options, - timerange: { - ...options.timerange, - interval: interval ? `>=${interval}s` : options.timerange.interval, - }, - }; + const optionsWithInterval = options.forceInterval + ? options + : { + ...options, + timerange: { + ...options.timerange, + interval: interval ? `>=${interval}s` : options.timerange.interval, + }, + }; - const metricsApiOptions = convertRequestToMetricsAPIOptions(optionsWithInterval); - const metricsApiResponse = await query(client, metricsApiOptions); - const totalGroupings = await queryTotalGroupings(client, metricsApiOptions); - const hasGroupBy = - Array.isArray(metricsApiOptions.groupBy) && metricsApiOptions.groupBy.length > 0; + const metricsApiOptions = convertRequestToMetricsAPIOptions(optionsWithInterval); + const metricsApiResponse = await query(client, metricsApiOptions); + const totalGroupings = await queryTotalGroupings(client, metricsApiOptions); + const hasGroupBy = + Array.isArray(metricsApiOptions.groupBy) && metricsApiOptions.groupBy.length > 0; - const pageInfo: MetricsExplorerPageInfo = { - total: totalGroupings, - afterKey: null, - }; + const pageInfo: MetricsExplorerPageInfo = { + total: totalGroupings, + afterKey: null, + }; - if (metricsApiResponse.info.afterKey) { - pageInfo.afterKey = metricsApiResponse.info.afterKey; - } + if (metricsApiResponse.info.afterKey) { + pageInfo.afterKey = metricsApiResponse.info.afterKey; + } - // If we have a groupBy but there are ZERO groupings returned then we need to - // return an empty array. Otherwise we transform the series to match the current schema. - const series = - hasGroupBy && totalGroupings === 0 - ? [] - : metricsApiResponse.series.map(transformSeries(hasGroupBy)); + // If we have a groupBy but there are ZERO groupings returned then we need to + // return an empty array. Otherwise we transform the series to match the current schema. + const series = + hasGroupBy && totalGroupings === 0 + ? [] + : metricsApiResponse.series.map(transformSeries(hasGroupBy)); - return response.ok({ - body: metricsExplorerResponseRT.encode({ series, pageInfo }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: metricsExplorerResponseRT.encode({ series, pageInfo }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/node_details/index.ts b/x-pack/plugins/infra/server/routes/node_details/index.ts index d407a9f65f983e..8e305226112bd3 100644 --- a/x-pack/plugins/infra/server/routes/node_details/index.ts +++ b/x-pack/plugins/infra/server/routes/node_details/index.ts @@ -34,38 +34,32 @@ export const initNodeDetailsRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe( - NodeDetailsRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - const source = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - sourceId - ); + const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe( + NodeDetailsRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + const source = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); - UsageCollector.countNode(nodeType); + UsageCollector.countNode(nodeType); - const options: InfraMetricsRequestOptions = { - nodeIds: { - nodeId, - cloudId, - }, - nodeType, - sourceConfiguration: source.configuration, - metrics, - timerange, - }; - return response.ok({ - body: NodeDetailsMetricDataResponseRT.encode({ - metrics: await libs.metrics.getMetrics(requestContext, options, request), - }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + const options: InfraMetricsRequestOptions = { + nodeIds: { + nodeId, + cloudId, + }, + nodeType, + sourceConfiguration: source.configuration, + metrics, + timerange, + }; + return response.ok({ + body: NodeDetailsMetricDataResponseRT.encode({ + metrics: await libs.metrics.getMetrics(requestContext, options, request), + }), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/overview/index.ts b/x-pack/plugins/infra/server/routes/overview/index.ts index 4102fd883e9127..fe988abcc2883d 100644 --- a/x-pack/plugins/infra/server/routes/overview/index.ts +++ b/x-pack/plugins/infra/server/routes/overview/index.ts @@ -36,77 +36,71 @@ export const initOverviewRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const overviewRequest = pipe( - OverviewRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const overviewRequest = pipe( + OverviewRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const client = createSearchClient(requestContext, framework); - const source = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - overviewRequest.sourceId - ); + const client = createSearchClient(requestContext, framework); + const source = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + overviewRequest.sourceId + ); - const inventoryModelFields = findInventoryFields('host', source.configuration.fields); + const inventoryModelFields = findInventoryFields('host', source.configuration.fields); - const params = { - index: source.configuration.metricAlias, - body: { - query: { - range: { - [source.configuration.fields.timestamp]: { - gte: overviewRequest.timerange.from, - lte: overviewRequest.timerange.to, - format: 'epoch_millis', - }, + const params = { + index: source.configuration.metricAlias, + body: { + query: { + range: { + [source.configuration.fields.timestamp]: { + gte: overviewRequest.timerange.from, + lte: overviewRequest.timerange.to, + format: 'epoch_millis', }, }, - aggs: { - hosts: { - cardinality: { - field: inventoryModelFields.id, - }, + }, + aggs: { + hosts: { + cardinality: { + field: inventoryModelFields.id, }, - cpu: { - avg: { - field: 'system.cpu.total.norm.pct', - }, + }, + cpu: { + avg: { + field: 'system.cpu.total.norm.pct', }, - memory: { - avg: { - field: 'system.memory.actual.used.pct', - }, + }, + memory: { + avg: { + field: 'system.memory.actual.used.pct', }, }, }, - }; + }, + }; - const esResponse = await client<{}, OverviewESAggResponse>(params); + const esResponse = await client<{}, OverviewESAggResponse>(params); - return response.ok({ - body: { - stats: { - hosts: { - type: 'number', - value: esResponse.aggregations?.hosts.value ?? 0, - }, - cpu: { - type: 'percent', - value: esResponse.aggregations?.cpu.value ?? 0, - }, - memory: { - type: 'percent', - value: esResponse.aggregations?.memory.value ?? 0, - }, + return response.ok({ + body: { + stats: { + hosts: { + type: 'number', + value: esResponse.aggregations?.hosts.value ?? 0, + }, + cpu: { + type: 'percent', + value: esResponse.aggregations?.cpu.value ?? 0, + }, + memory: { + type: 'percent', + value: esResponse.aggregations?.memory.value ?? 0, }, }, - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + }, + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/process_list/index.ts b/x-pack/plugins/infra/server/routes/process_list/index.ts index ec4ec21fc16749..f1ba7a7be03600 100644 --- a/x-pack/plugins/infra/server/routes/process_list/index.ts +++ b/x-pack/plugins/infra/server/routes/process_list/index.ts @@ -35,23 +35,17 @@ export const initProcessListRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const options = pipe( - ProcessListAPIRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const options = pipe( + ProcessListAPIRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const client = createSearchClient(requestContext, framework); - const processListResponse = await getProcessList(client, options); + const client = createSearchClient(requestContext, framework); + const processListResponse = await getProcessList(client, options); - return response.ok({ - body: ProcessListAPIResponseRT.encode(processListResponse), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: ProcessListAPIResponseRT.encode(processListResponse), + }); } ); @@ -64,23 +58,17 @@ export const initProcessListRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const options = pipe( - ProcessListAPIChartRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const options = pipe( + ProcessListAPIChartRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const client = createSearchClient(requestContext, framework); - const processListResponse = await getProcessListChart(client, options); + const client = createSearchClient(requestContext, framework); + const processListResponse = await getProcessListChart(client, options); - return response.ok({ - body: ProcessListAPIChartResponseRT.encode(processListResponse), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: ProcessListAPIChartResponseRT.encode(processListResponse), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/snapshot/index.ts b/x-pack/plugins/infra/server/routes/snapshot/index.ts index 07402a3f3ab5d9..aaf23085d0d600 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/index.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/index.ts @@ -31,29 +31,23 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const snapshotRequest = pipe( - SnapshotRequestRT.decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); + const snapshotRequest = pipe( + SnapshotRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); - const source = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - snapshotRequest.sourceId - ); + const source = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + snapshotRequest.sourceId + ); - UsageCollector.countNode(snapshotRequest.nodeType); - const client = createSearchClient(requestContext, framework); - const snapshotResponse = await getNodes(client, snapshotRequest, source); + UsageCollector.countNode(snapshotRequest.nodeType); + const client = createSearchClient(requestContext, framework); + const snapshotResponse = await getNodes(client, snapshotRequest, source); - return response.ok({ - body: SnapshotNodeResponseRT.encode(snapshotResponse), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: SnapshotNodeResponseRT.encode(snapshotResponse), + }); } ); }; diff --git a/x-pack/plugins/infra/server/routes/source/index.ts b/x-pack/plugins/infra/server/routes/source/index.ts index 5c3827e56ce79c..5ab3275f9ea9e0 100644 --- a/x-pack/plugins/infra/server/routes/source/index.ts +++ b/x-pack/plugins/infra/server/routes/source/index.ts @@ -44,34 +44,28 @@ export const initSourceRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const { type, sourceId } = request.params; + const { type, sourceId } = request.params; - const [source, logIndexStatus, metricIndicesExist, indexFields] = await Promise.all([ - libs.sources.getSourceConfiguration(requestContext.core.savedObjects.client, sourceId), - libs.sourceStatus.getLogIndexStatus(requestContext, sourceId), - libs.sourceStatus.hasMetricIndices(requestContext, sourceId), - libs.fields.getFields(requestContext, sourceId, typeToInfraIndexType(type)), - ]); + const [source, logIndexStatus, metricIndicesExist, indexFields] = await Promise.all([ + libs.sources.getSourceConfiguration(requestContext.core.savedObjects.client, sourceId), + libs.sourceStatus.getLogIndexStatus(requestContext, sourceId), + libs.sourceStatus.hasMetricIndices(requestContext, sourceId), + libs.fields.getFields(requestContext, sourceId, typeToInfraIndexType(type)), + ]); - if (!source) { - return response.notFound(); - } + if (!source) { + return response.notFound(); + } - const status: InfraSourceStatus = { - logIndicesExist: logIndexStatus !== 'missing', - metricIndicesExist, - indexFields, - }; + const status: InfraSourceStatus = { + logIndicesExist: logIndexStatus !== 'missing', + metricIndicesExist, + indexFields, + }; - return response.ok({ - body: SourceResponseRuntimeType.encode({ source: { ...source, status } }), - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + return response.ok({ + body: SourceResponseRuntimeType.encode({ source: { ...source, status } }), + }); } ); @@ -169,26 +163,20 @@ export const initSourceRoute = (libs: InfraBackendLibs) => { }, }, async (requestContext, request, response) => { - try { - const { type, sourceId } = request.params; - - const client = createSearchClient(requestContext, framework); - const source = await libs.sources.getSourceConfiguration( - requestContext.core.savedObjects.client, - sourceId - ); - const indexPattern = - type === 'metrics' ? source.configuration.metricAlias : source.configuration.logAlias; - const results = await hasData(indexPattern, client); - - return response.ok({ - body: { hasData: results }, - }); - } catch (error) { - return response.internalError({ - body: error.message, - }); - } + const { type, sourceId } = request.params; + + const client = createSearchClient(requestContext, framework); + const source = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); + const indexPattern = + type === 'metrics' ? source.configuration.metricAlias : source.configuration.logAlias; + const results = await hasData(indexPattern, client); + + return response.ok({ + body: { hasData: results }, + }); } ); }; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts index 363254d63a2c7e..afa36e5abe31a1 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts @@ -83,7 +83,7 @@ export const registerCreateRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts index b23a42b895af92..635ee015be5162 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts @@ -51,7 +51,7 @@ export const registerDocumentsRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts index 6237f4b6911bdb..3995448d13fbb9 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts @@ -44,7 +44,7 @@ export const registerGetRoutes = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); @@ -78,7 +78,7 @@ export const registerGetRoutes = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts index 52492c3ee6d27a..527b4d4277bf5f 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts @@ -44,28 +44,24 @@ export const registerPrivilegesRoute = ({ license, router, config }: RouteDepend }, } = ctx; - try { - const { has_all_requested: hasAllPrivileges, cluster } = await client.callAsCurrentUser( - 'transport.request', - { - path: '/_security/user/_has_privileges', - method: 'POST', - body: { - cluster: APP_CLUSTER_REQUIRED_PRIVILEGES, - }, - } - ); - - if (!hasAllPrivileges) { - privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(cluster); + const { has_all_requested: hasAllPrivileges, cluster } = await client.callAsCurrentUser( + 'transport.request', + { + path: '/_security/user/_has_privileges', + method: 'POST', + body: { + cluster: APP_CLUSTER_REQUIRED_PRIVILEGES, + }, } + ); - privilegesResult.hasAllPrivileges = hasAllPrivileges; - - return res.ok({ body: privilegesResult }); - } catch (e) { - return res.internalError({ body: e }); + if (!hasAllPrivileges) { + privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(cluster); } + + privilegesResult.hasAllPrivileges = hasAllPrivileges; + + return res.ok({ body: privilegesResult }); }) ); }; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts index efa2a84daca28c..f02aa0a8d5ed6d 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts @@ -52,7 +52,7 @@ export const registerSimulateRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts index 30cfe1b2505c2c..8776aace5ad789 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts @@ -65,7 +65,7 @@ export const registerUpdateRoute = ({ }); } - return res.internalError({ body: error }); + throw error; } }) ); diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index 11db9360749eaf..8a2db992a839da 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -78,11 +78,9 @@ export async function existingFieldsRoute(setup: CoreSetup, if (e.output.statusCode === 404) { return res.notFound({ body: e.output.payload.message }); } - return res.internalError({ body: e.output.payload.message }); + throw new Error(e.output.payload.message); } else { - return res.internalError({ - body: Boom.internal(e.message || e.name), - }); + throw e; } } } diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index 9094e5442dc511..57b3e59f4ad5c1 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -5,7 +5,6 @@ * 2.0. */ -import Boom from '@hapi/boom'; import { errors } from '@elastic/elasticsearch'; import DateMath from '@elastic/datemath'; import { schema } from '@kbn/config-schema'; @@ -122,11 +121,9 @@ export async function initFieldsRoute(setup: CoreSetup) { if (e.output.statusCode === 404) { return res.notFound(); } - return res.internalError(e.output.message); + throw new Error(e.output.message); } else { - return res.internalError({ - body: Boom.internal(e.message || e.name), - }); + throw e; } } } diff --git a/x-pack/plugins/lens/server/routes/telemetry.ts b/x-pack/plugins/lens/server/routes/telemetry.ts index cb8cf4b15f8d90..efcde9d14ebbe2 100644 --- a/x-pack/plugins/lens/server/routes/telemetry.ts +++ b/x-pack/plugins/lens/server/routes/telemetry.ts @@ -5,7 +5,6 @@ * 2.0. */ -import Boom from '@hapi/boom'; import { errors } from '@elastic/elasticsearch'; import { CoreSetup } from 'src/core/server'; import { schema } from '@kbn/config-schema'; @@ -84,11 +83,9 @@ export async function initLensUsageRoute(setup: CoreSetup) if (e.output.statusCode === 404) { return res.notFound(); } - return res.internalError(e.output.message); + throw new Error(e.output.message); } else { - return res.internalError({ - body: Boom.internal(e.message || e.name), - }); + throw e; } } } diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts b/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts index 7fcb73ffeb0086..86f87506dfc2c7 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts @@ -23,18 +23,14 @@ export function registerLicenseRoute({ router, plugins: { licensing } }: RouteDe }, async (ctx, req, res) => { const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; - try { - return res.ok({ - body: await putLicense({ - acknowledge: Boolean(req.query.acknowledge), - callAsCurrentUser, - licensing, - license: req.body, - }), - }); - } catch (e) { - return res.internalError({ body: e }); - } + return res.ok({ + body: await putLicense({ + acknowledge: Boolean(req.query.acknowledge), + callAsCurrentUser, + licensing, + license: req.body, + }), + }); } ); } diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts b/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts index c5cd11c022cfe6..dd441051872d2b 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts @@ -16,12 +16,8 @@ export function registerPermissionsRoute({ router.post({ path: addBasePath('/permissions'), validate: false }, async (ctx, req, res) => { const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; - try { - return res.ok({ - body: await getPermissions({ callAsCurrentUser, isSecurityEnabled }), - }); - } catch (e) { - return res.internalError({ body: e }); - } + return res.ok({ + body: await getPermissions({ callAsCurrentUser, isSecurityEnabled }), + }); }); } diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts b/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts index 820330c6a12044..bc5fb70f7dadd3 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts @@ -18,17 +18,13 @@ export function registerStartBasicRoute({ router, plugins: { licensing } }: Rout }, async (ctx, req, res) => { const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; - try { - return res.ok({ - body: await startBasic({ - acknowledge: Boolean(req.query.acknowledge), - callAsCurrentUser, - licensing, - }), - }); - } catch (e) { - return res.internalError({ body: e }); - } + return res.ok({ + body: await startBasic({ + acknowledge: Boolean(req.query.acknowledge), + callAsCurrentUser, + licensing, + }), + }); } ); } diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts b/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts index 570ae73d8aa613..6986e85e7d280d 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts @@ -12,21 +12,13 @@ import { addBasePath } from '../../helpers'; export function registerStartTrialRoutes({ router, plugins: { licensing } }: RouteDependencies) { router.get({ path: addBasePath('/start_trial'), validate: false }, async (ctx, req, res) => { const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; - try { - return res.ok({ body: await canStartTrial(callAsCurrentUser) }); - } catch (e) { - return res.internalError({ body: e }); - } + return res.ok({ body: await canStartTrial(callAsCurrentUser) }); }); router.post({ path: addBasePath('/start_trial'), validate: false }, async (ctx, req, res) => { const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; - try { - return res.ok({ - body: await startTrial({ callAsCurrentUser, licensing }), - }); - } catch (e) { - return res.internalError({ body: e }); - } + return res.ok({ + body: await startTrial({ callAsCurrentUser, licensing }), + }); }); } diff --git a/x-pack/plugins/logstash/server/routes/cluster/load.ts b/x-pack/plugins/logstash/server/routes/cluster/load.ts index f820ecdbeb4f34..ac7bc245e51ebc 100644 --- a/x-pack/plugins/logstash/server/routes/cluster/load.ts +++ b/x-pack/plugins/logstash/server/routes/cluster/load.ts @@ -29,7 +29,7 @@ export function registerClusterLoadRoute(router: LogstashPluginRouter) { if (err.status === 403) { return response.ok(); } - return response.internalError(); + throw err; } }) ); diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 654c3de7d81a96..4fada2d17bf5d5 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -368,7 +368,7 @@ export class MonitoringPlugin if (Boom.isBoom(err) || statusCode !== 500) { return res.customError({ statusCode, body: err }); } - return res.internalError(wrapError(err)); + throw wrapError(err).body; } }; diff --git a/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts index 1d9881c400ec8d..685aee16dc665c 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts @@ -88,7 +88,7 @@ export const register = (deps: RouteDependencies): void => { if (isEsError(error)) { return response.customError({ statusCode: error.statusCode, body: error }); } - return response.internalError({ body: error }); + throw error; } }; deps.router.post( diff --git a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts index 3a65bb2c54d952..89df5255d19e2e 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts @@ -95,7 +95,7 @@ export const register = (deps: RouteDependencies): void => { if (isEsError(error)) { return response.customError({ statusCode: error.statusCode, body: error }); } - return response.internalError({ body: error }); + throw error; } }; @@ -132,7 +132,7 @@ export const register = (deps: RouteDependencies): void => { if (isEsError(error)) { return response.customError({ statusCode: error.statusCode, body: error }); } - return response.internalError({ body: error }); + throw error; } }; diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts index 25d17d796b0ee4..cfec01da943ab7 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts @@ -78,10 +78,16 @@ describe('GET remote clusters', () => { const mockContext = xpackMocks.createRequestHandlerContext(); mockContext.core.elasticsearch.legacy.client = mockScopedClusterClient; - const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + if (asserts.statusCode === 500) { + await expect(handler(mockContext, mockRequest, kibanaResponseFactory)).rejects.toThrowError( + asserts.result as Error + ); + } else { + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); - expect(response.status).toBe(asserts.statusCode); - expect(response.payload).toEqual(asserts.result); + expect(response.status).toBe(asserts.statusCode); + expect(response.payload).toEqual(asserts.result); + } if (Array.isArray(asserts.apiArguments)) { for (const apiArguments of asserts.apiArguments) { diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts index 1445316cfec37c..fbb345203e48a8 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts @@ -63,7 +63,7 @@ export const register = (deps: RouteDependencies): void => { if (isEsError(error)) { return response.customError({ statusCode: error.statusCode, body: error }); } - return response.internalError({ body: error }); + throw error; } }; diff --git a/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts index 5e1fdbb2bc0db1..99fb7dd01adb13 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts @@ -100,7 +100,7 @@ export const register = (deps: RouteDependencies): void => { if (isEsError(error)) { return response.customError({ statusCode: error.statusCode, body: error }); } - return response.internalError({ body: error }); + throw error; } }; diff --git a/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts b/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts index cd35552ed5ad71..694ab3c467c1f9 100644 --- a/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts @@ -34,7 +34,7 @@ export const registerGetRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts b/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts index 854f4986e76867..90eabaa88b6410 100644 --- a/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts @@ -136,7 +136,7 @@ export const registerValidateIndexPatternRoute = ({ return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts index 14ff452a4dd546..bcb3a337aa7253 100644 --- a/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts @@ -43,7 +43,7 @@ export const registerCreateRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts index e94a1a80ce134d..4bbe73753e96cf 100644 --- a/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts @@ -45,7 +45,7 @@ export const registerDeleteRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts index 12b3f96e778358..a9a30c0370c5f3 100644 --- a/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts @@ -26,7 +26,7 @@ export const registerGetRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts index c560b41cc4385b..2ebfcc437f41e5 100644 --- a/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts @@ -42,7 +42,7 @@ export const registerStartRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts index 87cf2822d4f1b6..faaf377a2d833d 100644 --- a/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts @@ -43,7 +43,7 @@ export const registerStopRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts index 759e05dc2a3343..f77ae7829bb6c5 100644 --- a/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts @@ -41,7 +41,7 @@ export const registerSearchRoute = ({ if (isEsError(err)) { return response.customError({ statusCode: err.statusCode, body: err }); } - return response.internalError({ body: err }); + throw err; } }) ); diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index 65dd15b627c7f1..a9c5fa3577476d 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -149,7 +149,6 @@ describe('AuthenticationService', () => { expect(mockAuthToolkit.authenticated).toHaveBeenCalledTimes(1); expect(mockAuthToolkit.authenticated).toHaveBeenCalledWith(); expect(mockAuthToolkit.redirected).not.toHaveBeenCalled(); - expect(mockResponse.internalError).not.toHaveBeenCalled(); expect(authenticate).not.toHaveBeenCalled(); }); @@ -172,7 +171,6 @@ describe('AuthenticationService', () => { requestHeaders: mockAuthHeaders, }); expect(mockAuthToolkit.redirected).not.toHaveBeenCalled(); - expect(mockResponse.internalError).not.toHaveBeenCalled(); expect(authenticate).toHaveBeenCalledTimes(1); expect(authenticate).toHaveBeenCalledWith(mockRequest); @@ -201,7 +199,6 @@ describe('AuthenticationService', () => { responseHeaders: mockAuthResponseHeaders, }); expect(mockAuthToolkit.redirected).not.toHaveBeenCalled(); - expect(mockResponse.internalError).not.toHaveBeenCalled(); expect(authenticate).toHaveBeenCalledTimes(1); expect(authenticate).toHaveBeenCalledWith(mockRequest); @@ -223,7 +220,6 @@ describe('AuthenticationService', () => { 'WWW-Authenticate': 'Negotiate', }); expect(mockAuthToolkit.authenticated).not.toHaveBeenCalled(); - expect(mockResponse.internalError).not.toHaveBeenCalled(); }); it('rejects with `Internal Server Error` and log error when `authenticate` throws unhandled exception', async () => { @@ -231,15 +227,12 @@ describe('AuthenticationService', () => { const failureReason = new Error('something went wrong'); authenticate.mockRejectedValue(failureReason); - await authHandler(httpServerMock.createKibanaRequest(), mockResponse, mockAuthToolkit); - - expect(mockResponse.internalError).toHaveBeenCalledTimes(1); - const [[error]] = mockResponse.internalError.mock.calls; - expect(error).toBeUndefined(); + await expect( + authHandler(httpServerMock.createKibanaRequest(), mockResponse, mockAuthToolkit) + ).rejects.toThrow(failureReason); expect(mockAuthToolkit.authenticated).not.toHaveBeenCalled(); expect(mockAuthToolkit.redirected).not.toHaveBeenCalled(); - expect(logger.error).toHaveBeenCalledWith(failureReason); }); it('rejects with original `badRequest` error when `authenticate` fails to authenticate user', async () => { diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index 0543e2abd60df3..6848d7a3c7df63 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -93,13 +93,7 @@ export class AuthenticationService { }); } - let authenticationResult; - try { - authenticationResult = await this.authenticator.authenticate(request); - } catch (err) { - this.logger.error(err); - return response.internalError(); - } + const authenticationResult = await this.authenticator.authenticate(request); if (authenticationResult.succeeded()) { return t.authenticated({ diff --git a/x-pack/plugins/security/server/routes/authentication/common.test.ts b/x-pack/plugins/security/server/routes/authentication/common.test.ts index 38f832cc051ddb..654e4fc18f195a 100644 --- a/x-pack/plugins/security/server/routes/authentication/common.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/common.test.ts @@ -413,11 +413,9 @@ describe('Common authentication routes', () => { body: { providerType: 'saml', providerName: 'saml1', currentURL: '/some-url' }, }); - await expect(routeHandler(mockContext, request, kibanaResponseFactory)).resolves.toEqual({ - status: 500, - payload: 'Internal Error', - options: {}, - }); + await expect(routeHandler(mockContext, request, kibanaResponseFactory)).rejects.toThrow( + unhandledException + ); }); it('returns 401 if login fails.', async () => { @@ -683,11 +681,9 @@ describe('Common authentication routes', () => { authc.acknowledgeAccessAgreement.mockRejectedValue(unhandledException); const request = httpServerMock.createKibanaRequest(); - await expect(routeHandler(mockContext, request, kibanaResponseFactory)).resolves.toEqual({ - status: 500, - payload: 'Internal Error', - options: {}, - }); + await expect(routeHandler(mockContext, request, kibanaResponseFactory)).rejects.toThrowError( + unhandledException + ); }); it('returns 204 if successfully acknowledged.', async () => { diff --git a/x-pack/plugins/security/server/routes/authentication/common.ts b/x-pack/plugins/security/server/routes/authentication/common.ts index 0b0915198f3d40..f1d9aab74548a4 100644 --- a/x-pack/plugins/security/server/routes/authentication/common.ts +++ b/x-pack/plugins/security/server/routes/authentication/common.ts @@ -142,28 +142,23 @@ export function defineCommonRoutes({ logger.info(`Logging in with provider "${providerName}" (${providerType})`); const redirectURL = parseNext(currentURL, basePath.serverBasePath); - try { - const authenticationResult = await getAuthenticationService().login(request, { - provider: { name: providerName }, - redirectURL, - value: getLoginAttemptForProviderType(providerType, redirectURL, params), - }); - - if (authenticationResult.redirected() || authenticationResult.succeeded()) { - return response.ok({ - body: { location: authenticationResult.redirectURL || redirectURL }, - headers: authenticationResult.authResponseHeaders, - }); - } - - return response.unauthorized({ - body: authenticationResult.error, + const authenticationResult = await getAuthenticationService().login(request, { + provider: { name: providerName }, + redirectURL, + value: getLoginAttemptForProviderType(providerType, redirectURL, params), + }); + + if (authenticationResult.redirected() || authenticationResult.succeeded()) { + return response.ok({ + body: { location: authenticationResult.redirectURL || redirectURL }, headers: authenticationResult.authResponseHeaders, }); - } catch (err) { - logger.error(err); - return response.internalError(); } + + return response.unauthorized({ + body: authenticationResult.error, + headers: authenticationResult.authResponseHeaders, + }); }) ); @@ -178,12 +173,7 @@ export function defineCommonRoutes({ }); } - try { - await getAuthenticationService().acknowledgeAccessAgreement(request); - } catch (err) { - logger.error(err); - return response.internalError(); - } + await getAuthenticationService().acknowledgeAccessAgreement(request); return response.noContent(); }) diff --git a/x-pack/plugins/security/server/routes/authentication/saml.test.ts b/x-pack/plugins/security/server/routes/authentication/saml.test.ts index 5a08c3e1797047..73cba46f46ea70 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.test.ts @@ -76,16 +76,14 @@ describe('SAML authentication routes', () => { const unhandledException = new Error('Something went wrong.'); authc.login.mockRejectedValue(unhandledException); - const internalServerErrorResponse = Symbol('error'); const responseFactory = httpServerMock.createResponseFactory(); - responseFactory.internalError.mockReturnValue(internalServerErrorResponse as any); const request = httpServerMock.createKibanaRequest({ body: { SAMLResponse: 'saml-response' }, }); - await expect(routeHandler({} as any, request, responseFactory)).resolves.toBe( - internalServerErrorResponse + await expect(routeHandler({} as any, request, responseFactory)).rejects.toThrow( + unhandledException ); expect(authc.login).toHaveBeenCalledWith(request, { diff --git a/x-pack/plugins/security/server/routes/authentication/saml.ts b/x-pack/plugins/security/server/routes/authentication/saml.ts index 9fee03cbc614a1..257b95ec707b40 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.ts @@ -30,28 +30,23 @@ export function defineSAMLRoutes({ options: { authRequired: false, xsrfRequired: false }, }, async (context, request, response) => { - try { - // When authenticating using SAML we _expect_ to redirect to the Kibana target location. - const authenticationResult = await getAuthenticationService().login(request, { - provider: { type: SAMLAuthenticationProvider.type }, - value: { - type: SAMLLogin.LoginWithSAMLResponse, - samlResponse: request.body.SAMLResponse, - relayState: request.body.RelayState, - }, - }); - - if (authenticationResult.redirected()) { - return response.redirected({ - headers: { location: authenticationResult.redirectURL! }, - }); - } + // When authenticating using SAML we _expect_ to redirect to the Kibana target location. + const authenticationResult = await getAuthenticationService().login(request, { + provider: { type: SAMLAuthenticationProvider.type }, + value: { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: request.body.SAMLResponse, + relayState: request.body.RelayState, + }, + }); - return response.unauthorized({ body: authenticationResult.error }); - } catch (err) { - logger.error(err); - return response.internalError(); + if (authenticationResult.redirected()) { + return response.redirected({ + headers: { location: authenticationResult.redirectURL! }, + }); } + + return response.unauthorized({ body: authenticationResult.error }); } ); } diff --git a/x-pack/plugins/security/server/routes/session_management/info.test.ts b/x-pack/plugins/security/server/routes/session_management/info.test.ts index 6ed50b50c0eb91..84db94f38d5829 100644 --- a/x-pack/plugins/security/server/routes/session_management/info.test.ts +++ b/x-pack/plugins/security/server/routes/session_management/info.test.ts @@ -60,11 +60,7 @@ describe('Info session routes', () => { request, kibanaResponseFactory ) - ).resolves.toEqual({ - status: 500, - options: {}, - payload: 'Internal Error', - }); + ).rejects.toThrowError(unhandledException); expect(session.get).toHaveBeenCalledWith(request); }); diff --git a/x-pack/plugins/security/server/routes/session_management/info.ts b/x-pack/plugins/security/server/routes/session_management/info.ts index f47d896eb55e00..6cab44509f162e 100644 --- a/x-pack/plugins/security/server/routes/session_management/info.ts +++ b/x-pack/plugins/security/server/routes/session_management/info.ts @@ -11,30 +11,25 @@ import { RouteDefinitionParams } from '..'; /** * Defines routes required for the session info. */ -export function defineSessionInfoRoutes({ router, logger, getSession }: RouteDefinitionParams) { +export function defineSessionInfoRoutes({ router, getSession }: RouteDefinitionParams) { router.get( { path: '/internal/security/session', validate: false }, async (_context, request, response) => { - try { - const sessionValue = await getSession().get(request); - if (sessionValue) { - return response.ok({ - body: { - // We can't rely on the client's system clock, so in addition to returning expiration timestamps, we also return - // the current server time -- that way the client can calculate the relative time to expiration. - now: Date.now(), - idleTimeoutExpiration: sessionValue.idleTimeoutExpiration, - lifespanExpiration: sessionValue.lifespanExpiration, - provider: sessionValue.provider, - } as SessionInfo, - }); - } - - return response.noContent(); - } catch (err) { - logger.error(`Error retrieving user session: ${err.message}`); - return response.internalError(); + const sessionValue = await getSession().get(request); + if (sessionValue) { + return response.ok({ + body: { + // We can't rely on the client's system clock, so in addition to returning expiration timestamps, we also return + // the current server time -- that way the client can calculate the relative time to expiration. + now: Date.now(), + idleTimeoutExpiration: sessionValue.idleTimeoutExpiration, + lifespanExpiration: sessionValue.lifespanExpiration, + provider: sessionValue.provider, + } as SessionInfo, + }); } + + return response.noContent(); } ); } diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.ts b/x-pack/plugins/security/server/routes/views/access_agreement.ts index ff67e1af1e7baa..daf697bd23448b 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.ts @@ -46,20 +46,15 @@ export function defineAccessAgreementRoutes({ // It's not guaranteed that we'll have session for the authenticated user (e.g. when user is // authenticated with the help of HTTP authentication), that means we should safely check if // we have it and can get a corresponding configuration. - try { - const sessionValue = await getSession().get(request); - const accessAgreement = - (sessionValue && - config.authc.providers[ - sessionValue.provider.type as keyof ConfigType['authc']['providers'] - ]?.[sessionValue.provider.name]?.accessAgreement?.message) || - ''; + const sessionValue = await getSession().get(request); + const accessAgreement = + (sessionValue && + config.authc.providers[ + sessionValue.provider.type as keyof ConfigType['authc']['providers'] + ]?.[sessionValue.provider.name]?.accessAgreement?.message) || + ''; - return response.ok({ body: { accessAgreement } }); - } catch (err) { - logger.error(err); - return response.internalError(); - } + return response.ok({ body: { accessAgreement } }); }) ); } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts index 020b70ca0553ca..95070b10b95501 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts @@ -78,7 +78,7 @@ export function registerDownloadArtifactRoute( }; if (validateDownload && !downloadArtifactResponseSchema.is(artifact)) { - return res.internalError({ body: 'Artifact failed to validate.' }); + throw new Error('Artifact failed to validate.'); } else { return res.ok(artifact); } @@ -103,7 +103,7 @@ export function registerDownloadArtifactRoute( if (err?.output?.statusCode === 404) { return res.notFound({ body: `No artifact found for ${id}` }); } else { - return res.internalError({ body: err }); + throw err; } }); } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index ad5381d2ee36d6..134ce99784bfb6 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -62,57 +62,52 @@ export const getMetadataListRequestHandler = function ( SecuritySolutionRequestHandlerContext > { return async (context, request, response) => { - try { - const agentService = endpointAppContext.service.getAgentService(); - if (agentService === undefined) { - throw new Error('agentService not available'); - } + const agentService = endpointAppContext.service.getAgentService(); + if (agentService === undefined) { + throw new Error('agentService not available'); + } - const metadataRequestContext: MetadataRequestContext = { - endpointAppContextService: endpointAppContext.service, - logger, - requestHandlerContext: context, - }; + const metadataRequestContext: MetadataRequestContext = { + endpointAppContextService: endpointAppContext.service, + logger, + requestHandlerContext: context, + }; - const unenrolledAgentIds = await findAllUnenrolledAgentIds( - agentService, - context.core.savedObjects.client, - context.core.elasticsearch.client.asCurrentUser - ); + const unenrolledAgentIds = await findAllUnenrolledAgentIds( + agentService, + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser + ); - const statusIDs = request?.body?.filters?.host_status?.length - ? await findAgentIDsByStatus( - agentService, - context.core.savedObjects.client, - context.core.elasticsearch.client.asCurrentUser, - request.body?.filters?.host_status - ) - : undefined; + const statusIDs = request?.body?.filters?.host_status?.length + ? await findAgentIDsByStatus( + agentService, + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser, + request.body?.filters?.host_status + ) + : undefined; - const queryStrategy = await endpointAppContext.service - ?.getMetadataService() - ?.queryStrategy(context.core.savedObjects.client, queryStrategyVersion); + const queryStrategy = await endpointAppContext.service + ?.getMetadataService() + ?.queryStrategy(context.core.savedObjects.client, queryStrategyVersion); - const queryParams = await kibanaRequestToMetadataListESQuery( - request, - endpointAppContext, - queryStrategy!, - { - unenrolledAgentIds: unenrolledAgentIds.concat(IGNORED_ELASTIC_AGENT_IDS), - statusAgentIDs: statusIDs, - } - ); + const queryParams = await kibanaRequestToMetadataListESQuery( + request, + endpointAppContext, + queryStrategy!, + { + unenrolledAgentIds: unenrolledAgentIds.concat(IGNORED_ELASTIC_AGENT_IDS), + statusAgentIDs: statusIDs, + } + ); - const hostListQueryResult = queryStrategy!.queryResponseToHostListResult( - await context.core.elasticsearch.legacy.client.callAsCurrentUser('search', queryParams) - ); - return response.ok({ - body: await mapToHostResultList(queryParams, hostListQueryResult, metadataRequestContext), - }); - } catch (err) { - logger.warn(JSON.stringify(err, null, 2)); - return response.internalError({ body: err }); - } + const hostListQueryResult = queryStrategy!.queryResponseToHostListResult( + await context.core.elasticsearch.legacy.client.callAsCurrentUser('search', queryParams) + ); + return response.ok({ + body: await mapToHostResultList(queryParams, hostListQueryResult, metadataRequestContext), + }); }; }; @@ -129,7 +124,7 @@ export const getMetadataRequestHandler = function ( return async (context, request, response) => { const agentService = endpointAppContext.service.getAgentService(); if (agentService === undefined) { - return response.internalError({ body: 'agentService not available' }); + throw new Error('agentService not available'); } const metadataRequestContext: MetadataRequestContext = { @@ -156,7 +151,7 @@ export const getMetadataRequestHandler = function ( body: { message: err.message }, }); } - return response.internalError({ body: err }); + throw err; } }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts index 132cc6dae58fff..d6b50becc2d029 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts @@ -49,11 +49,7 @@ describe('test policy response handler', () => { it('should return the latest policy response for a host', async () => { const response = createSearchResponse(new EndpointDocGenerator().generatePolicyResponse()); - const hostPolicyResponseHandler = getHostPolicyResponseHandler({ - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - }); + const hostPolicyResponseHandler = getHostPolicyResponseHandler(); mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response)); const mockRequest = httpServerMock.createKibanaRequest({ @@ -72,11 +68,7 @@ describe('test policy response handler', () => { }); it('should return not found when there is no response policy for host', async () => { - const hostPolicyResponseHandler = getHostPolicyResponseHandler({ - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - }); + const hostPolicyResponseHandler = getHostPolicyResponseHandler(); mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(createSearchResponse()) diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts index 3027892ff37452..ec1fad80701b62 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.ts @@ -16,25 +16,23 @@ import { EndpointAppContext } from '../../types'; import { getAgentPolicySummary, getPolicyResponseByAgentId } from './service'; import { GetAgentSummaryResponse } from '../../../../common/endpoint/types'; -export const getHostPolicyResponseHandler = function ( - endpointAppContext: EndpointAppContext -): RequestHandler, undefined> { +export const getHostPolicyResponseHandler = function (): RequestHandler< + undefined, + TypeOf, + undefined +> { return async (context, request, response) => { - try { - const doc = await getPolicyResponseByAgentId( - policyIndexPattern, - request.query.agentId, - context.core.elasticsearch.legacy.client - ); - - if (doc) { - return response.ok({ body: doc }); - } + const doc = await getPolicyResponseByAgentId( + policyIndexPattern, + request.query.agentId, + context.core.elasticsearch.legacy.client + ); - return response.notFound({ body: 'Policy Response Not Found' }); - } catch (err) { - return response.internalError({ body: err }); + if (doc) { + return response.ok({ body: doc }); } + + return response.notFound({ body: 'Policy Response Not Found' }); }; }; @@ -42,31 +40,26 @@ export const getAgentPolicySummaryHandler = function ( endpointAppContext: EndpointAppContext ): RequestHandler, undefined> { return async (context, request, response) => { - try { - const result = await getAgentPolicySummary( - endpointAppContext, - context.core.savedObjects.client, - context.core.elasticsearch.client.asCurrentUser, - request.query.package_name, - request.query?.policy_id || undefined - ); - const responseBody = { - package: request.query.package_name, - versions_count: { ...result }, - }; + const result = await getAgentPolicySummary( + endpointAppContext, + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser, + request.query.package_name, + request.query?.policy_id || undefined + ); + const responseBody = { + package: request.query.package_name, + versions_count: { ...result }, + }; - const body: GetAgentSummaryResponse = { - summary_response: request.query?.policy_id - ? { ...responseBody, ...{ policy_id: request.query?.policy_id } } - : responseBody, - }; + const body: GetAgentSummaryResponse = { + summary_response: request.query?.policy_id + ? { ...responseBody, ...{ policy_id: request.query?.policy_id } } + : responseBody, + }; - return response.ok({ - body, - }); - } catch (err) { - endpointAppContext.logFactory.get('metadata').error(JSON.stringify(err, null, 2)); - return response.internalError({ body: err }); - } + return response.ok({ + body, + }); }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts index 0c199890c205d9..50a68debc11255 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts @@ -26,7 +26,7 @@ export function registerPolicyRoutes(router: IRouter, endpointAppContext: Endpoi validate: GetPolicyResponseSchema, options: { authRequired: true }, }, - getHostPolicyResponseHandler(endpointAppContext) + getHostPolicyResponseHandler() ); router.get( diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts index 398666b40ae380..617a1907cf4bef 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts @@ -6,7 +6,6 @@ */ import { IRouter } from 'kibana/server'; -import { EndpointAppContext } from '../types'; import { validateEvents, validateEntities, @@ -17,16 +16,14 @@ import { handleTree } from './resolver/tree/handler'; import { handleEntities } from './resolver/entity'; import { handleEvents } from './resolver/events'; -export function registerResolverRoutes(router: IRouter, endpointAppContext: EndpointAppContext) { - const log = endpointAppContext.logFactory.get('resolver'); - +export function registerResolverRoutes(router: IRouter) { router.post( { path: '/api/endpoint/resolver/tree', validate: validateTree, options: { authRequired: true }, }, - handleTree(log) + handleTree() ); router.post( @@ -35,7 +32,7 @@ export function registerResolverRoutes(router: IRouter, endpointAppContext: Endp validate: validateEvents, options: { authRequired: true }, }, - handleEvents(log) + handleEvents() ); /** diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts index edbfc4a4423e2d..6b574b3bcbc278 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts @@ -6,7 +6,7 @@ */ import { TypeOf } from '@kbn/config-schema'; -import { RequestHandler, Logger } from 'kibana/server'; +import { RequestHandler } from 'kibana/server'; import { ResolverPaginatedEvents, SafeResolverEvent } from '../../../../common/endpoint/types'; import { validateEvents } from '../../../../common/endpoint/schema/resolver'; import { EventsQuery } from './queries/events'; @@ -28,11 +28,8 @@ function createEvents( /** * This function handles the `/events` api and returns an array of events and a cursor if more events exist than were * requested. - * @param log a logger object */ -export function handleEvents( - log: Logger -): RequestHandler< +export function handleEvents(): RequestHandler< unknown, TypeOf, TypeOf @@ -42,21 +39,16 @@ export function handleEvents( query: { limit, afterEvent }, body, } = req; - try { - const client = context.core.elasticsearch.client; - const query = new EventsQuery({ - pagination: PaginationBuilder.createBuilder(limit, afterEvent), - indexPatterns: body.indexPatterns, - timeRange: body.timeRange, - }); - const results = await query.search(client, body.filter); + const client = context.core.elasticsearch.client; + const query = new EventsQuery({ + pagination: PaginationBuilder.createBuilder(limit, afterEvent), + indexPatterns: body.indexPatterns, + timeRange: body.timeRange, + }); + const results = await query.search(client, body.filter); - return res.ok({ - body: createEvents(results, PaginationBuilder.buildCursorRequestLimit(limit, results)), - }); - } catch (err) { - log.warn(err); - return res.internalError({ body: err }); - } + return res.ok({ + body: createEvents(results, PaginationBuilder.buildCursorRequestLimit(limit, results)), + }); }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts index e7b4046b3532ad..675c861b984de2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts @@ -5,25 +5,18 @@ * 2.0. */ -import { RequestHandler, Logger } from 'kibana/server'; +import { RequestHandler } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; import { validateTree } from '../../../../../common/endpoint/schema/resolver'; import { Fetcher } from './utils/fetch'; -export function handleTree( - log: Logger -): RequestHandler> { +export function handleTree(): RequestHandler> { return async (context, req, res) => { - try { - const client = context.core.elasticsearch.client; - const fetcher = new Fetcher(client); - const body = await fetcher.tree(req.body); - return res.ok({ - body, - }); - } catch (err) { - log.warn(err); - return res.internalError({ body: 'Error retrieving tree.' }); - } + const client = context.core.elasticsearch.client; + const fetcher = new Fetcher(client); + const body = await fetcher.tree(req.body); + return res.ok({ + body, + }); }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts index 50aff5217a12ea..2179397c237044 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts @@ -112,7 +112,7 @@ describe('handlers', () => { }); describe('getTrustedAppsDeleteRouteHandler', () => { - const deleteTrustedAppHandler = getTrustedAppsDeleteRouteHandler(appContextMock); + const deleteTrustedAppHandler = getTrustedAppsDeleteRouteHandler(); it('should return ok when trusted app deleted', async () => { const mockResponse = httpServerMock.createResponseFactory(); @@ -146,19 +146,18 @@ describe('handlers', () => { exceptionsListClient.deleteExceptionListItem.mockRejectedValue(error); - await deleteTrustedAppHandler( - createHandlerContextMock(), - httpServerMock.createKibanaRequest({ params: { id: '123' } }), - mockResponse - ); - - assertResponse(mockResponse, 'internalError', error); - expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(error); + await expect( + deleteTrustedAppHandler( + createHandlerContextMock(), + httpServerMock.createKibanaRequest({ params: { id: '123' } }), + mockResponse + ) + ).rejects.toThrowError(error); }); }); describe('getTrustedAppsCreateRouteHandler', () => { - const createTrustedAppHandler = getTrustedAppsCreateRouteHandler(appContextMock); + const createTrustedAppHandler = getTrustedAppsCreateRouteHandler(); it('should return ok with body when trusted app created', async () => { const mockResponse = httpServerMock.createResponseFactory(); @@ -180,19 +179,18 @@ describe('handlers', () => { exceptionsListClient.createExceptionListItem.mockRejectedValue(error); - await createTrustedAppHandler( - createHandlerContextMock(), - httpServerMock.createKibanaRequest({ body: NEW_TRUSTED_APP }), - mockResponse - ); - - assertResponse(mockResponse, 'internalError', error); - expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(error); + await expect( + createTrustedAppHandler( + createHandlerContextMock(), + httpServerMock.createKibanaRequest({ body: NEW_TRUSTED_APP }), + mockResponse + ) + ).rejects.toThrowError(error); }); }); describe('getTrustedAppsListRouteHandler', () => { - const getTrustedAppsListHandler = getTrustedAppsListRouteHandler(appContextMock); + const getTrustedAppsListHandler = getTrustedAppsListRouteHandler(); it('should return ok with list when no errors', async () => { const mockResponse = httpServerMock.createResponseFactory(); @@ -224,19 +222,18 @@ describe('handlers', () => { exceptionsListClient.findExceptionListItem.mockRejectedValue(error); - await getTrustedAppsListHandler( - createHandlerContextMock(), - httpServerMock.createKibanaRequest({ body: NEW_TRUSTED_APP }), - mockResponse - ); - - assertResponse(mockResponse, 'internalError', error); - expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(error); + await expect( + getTrustedAppsListHandler( + createHandlerContextMock(), + httpServerMock.createKibanaRequest({ body: NEW_TRUSTED_APP }), + mockResponse + ) + ).rejects.toThrowError(error); }); }); describe('getTrustedAppsSummaryHandler', () => { - const getTrustedAppsSummaryHandler = getTrustedAppsSummaryRouteHandler(appContextMock); + const getTrustedAppsSummaryHandler = getTrustedAppsSummaryRouteHandler(); it('should return ok with list when no errors', async () => { const mockResponse = httpServerMock.createResponseFactory(); @@ -290,14 +287,13 @@ describe('handlers', () => { exceptionsListClient.findExceptionListItem.mockRejectedValue(error); - await getTrustedAppsSummaryHandler( - createHandlerContextMock(), - httpServerMock.createKibanaRequest(), - mockResponse - ); - - assertResponse(mockResponse, 'internalError', error); - expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(error); + await expect( + getTrustedAppsSummaryHandler( + createHandlerContextMock(), + httpServerMock.createKibanaRequest(), + mockResponse + ) + ).rejects.toThrowError(error); }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts index e7ceeee00c3069..fd5160472986f5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts @@ -16,7 +16,6 @@ import { PostTrustedAppCreateRequest, } from '../../../../common/endpoint/types'; -import { EndpointAppContext } from '../../types'; import { createTrustedApp, deleteTrustedApp, @@ -37,16 +36,12 @@ const exceptionListClientFromContext = ( return exceptionLists; }; -export const getTrustedAppsDeleteRouteHandler = ( - endpointAppContext: EndpointAppContext -): RequestHandler< +export const getTrustedAppsDeleteRouteHandler = (): RequestHandler< DeleteTrustedAppsRequestParams, unknown, unknown, SecuritySolutionRequestHandlerContext > => { - const logger = endpointAppContext.logFactory.get('trusted_apps'); - return async (context, req, res) => { try { await deleteTrustedApp(exceptionListClientFromContext(context), req.params); @@ -56,75 +51,47 @@ export const getTrustedAppsDeleteRouteHandler = ( if (error instanceof MissingTrustedAppException) { return res.notFound({ body: `trusted app id [${req.params.id}] not found` }); } else { - logger.error(error); - return res.internalError({ body: error }); + throw error; } } }; }; -export const getTrustedAppsListRouteHandler = ( - endpointAppContext: EndpointAppContext -): RequestHandler< +export const getTrustedAppsListRouteHandler = (): RequestHandler< unknown, GetTrustedAppsListRequest, unknown, SecuritySolutionRequestHandlerContext > => { - const logger = endpointAppContext.logFactory.get('trusted_apps'); - return async (context, req, res) => { - try { - return res.ok({ - body: await getTrustedAppsList(exceptionListClientFromContext(context), req.query), - }); - } catch (error) { - logger.error(error); - return res.internalError({ body: error }); - } + return res.ok({ + body: await getTrustedAppsList(exceptionListClientFromContext(context), req.query), + }); }; }; -export const getTrustedAppsCreateRouteHandler = ( - endpointAppContext: EndpointAppContext -): RequestHandler< +export const getTrustedAppsCreateRouteHandler = (): RequestHandler< unknown, unknown, PostTrustedAppCreateRequest, SecuritySolutionRequestHandlerContext > => { - const logger = endpointAppContext.logFactory.get('trusted_apps'); - return async (context, req, res) => { - try { - return res.ok({ - body: await createTrustedApp(exceptionListClientFromContext(context), req.body), - }); - } catch (error) { - logger.error(error); - return res.internalError({ body: error }); - } + return res.ok({ + body: await createTrustedApp(exceptionListClientFromContext(context), req.body), + }); }; }; -export const getTrustedAppsSummaryRouteHandler = ( - endpointAppContext: EndpointAppContext -): RequestHandler< +export const getTrustedAppsSummaryRouteHandler = (): RequestHandler< unknown, unknown, PostTrustedAppCreateRequest, SecuritySolutionRequestHandlerContext > => { - const logger = endpointAppContext.logFactory.get('trusted_apps'); - return async (context, req, res) => { - try { - return res.ok({ - body: await getTrustedAppsSummary(exceptionListClientFromContext(context)), - }); - } catch (error) { - logger.error(error); - return res.internalError({ body: error }); - } + return res.ok({ + body: await getTrustedAppsSummary(exceptionListClientFromContext(context)), + }); }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts index af23bc7025bf13..4a17b088dc8714 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts @@ -22,13 +22,9 @@ import { getTrustedAppsListRouteHandler, getTrustedAppsSummaryRouteHandler, } from './handlers'; -import { EndpointAppContext } from '../../types'; import { SecuritySolutionPluginRouter } from '../../../types'; -export const registerTrustedAppsRoutes = ( - router: SecuritySolutionPluginRouter, - endpointAppContext: EndpointAppContext -) => { +export const registerTrustedAppsRoutes = (router: SecuritySolutionPluginRouter) => { // DELETE one router.delete( { @@ -36,7 +32,7 @@ export const registerTrustedAppsRoutes = ( validate: DeleteTrustedAppsRequestSchema, options: { authRequired: true }, }, - getTrustedAppsDeleteRouteHandler(endpointAppContext) + getTrustedAppsDeleteRouteHandler() ); // GET list @@ -46,7 +42,7 @@ export const registerTrustedAppsRoutes = ( validate: GetTrustedAppsRequestSchema, options: { authRequired: true }, }, - getTrustedAppsListRouteHandler(endpointAppContext) + getTrustedAppsListRouteHandler() ); // CREATE @@ -56,7 +52,7 @@ export const registerTrustedAppsRoutes = ( validate: PostTrustedAppCreateRequestSchema, options: { authRequired: true }, }, - getTrustedAppsCreateRouteHandler(endpointAppContext) + getTrustedAppsCreateRouteHandler() ); // SUMMARY @@ -66,6 +62,6 @@ export const registerTrustedAppsRoutes = ( validate: false, options: { authRequired: true }, }, - getTrustedAppsSummaryRouteHandler(endpointAppContext) + getTrustedAppsSummaryRouteHandler() ); }; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 5f70b6cdc641e2..b675920977df15 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -198,9 +198,9 @@ export class Plugin implements IPlugin { jest.fn().mockRejectedValueOnce(new Error()), // Call to 'sr.policies' ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -140,8 +139,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -174,8 +172,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -286,8 +283,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [{}, jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -315,8 +311,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -372,8 +367,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -398,8 +392,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts b/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts index 3c656194aa8542..fa127880fd806a 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts @@ -51,7 +51,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -92,7 +92,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -131,7 +131,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -167,7 +167,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -222,7 +222,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -263,7 +263,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -323,7 +323,7 @@ export function registerPolicyRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts index 42c64ff3874feb..35dce2c5d558fa 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts @@ -108,8 +108,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { jest.fn().mockRejectedValueOnce(new Error()), ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -214,8 +213,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { jest.fn().mockRejectedValueOnce(new Error()), ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -292,8 +290,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { jest.fn().mockRejectedValueOnce(new Error('Error getting pluggins')), ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError('Error getting pluggins'); }); }); @@ -328,9 +325,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { const error = new Error('Oh no!'); router.callAsCurrentUserResponses = [{}, jest.fn().mockRejectedValueOnce(error)]; - const response = await router.runRequest(mockRequest); - expect(response.body.message).toEqual(error.message); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(error); }); }); @@ -358,8 +353,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts index f8a5f01e4cf3df..c9945bb172e6c3 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts @@ -66,7 +66,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } // If a managed repository, we also need to check if a policy is associated to it @@ -121,7 +121,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } const { @@ -203,7 +203,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -244,7 +244,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -285,7 +285,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -329,7 +329,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -371,7 +371,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -412,7 +412,7 @@ export function registerRepositoriesRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/restore.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/restore.test.ts index bbf90841e774eb..fe33331522daa8 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/restore.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/restore.test.ts @@ -48,8 +48,7 @@ describe('[Snapshot and Restore API Routes] Restore', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -111,8 +110,7 @@ describe('[Snapshot and Restore API Routes] Restore', () => { it('should throw if ES error', async () => { router.callAsCurrentUserResponses = [jest.fn().mockRejectedValueOnce(new Error())]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/restore.ts b/x-pack/plugins/snapshot_restore/server/routes/api/restore.ts index df2bc20b026c9b..c4300bafc75fbb 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/restore.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/restore.ts @@ -87,7 +87,7 @@ export function registerRestoreRoutes({ router, license, lib: { isEsError } }: R }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -124,7 +124,7 @@ export function registerRestoreRoutes({ router, license, lib: { isEsError } }: R }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts index bcdd27a1f100c3..97eb34b4aaa732 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts @@ -144,8 +144,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { jest.fn().mockRejectedValueOnce(new Error('Error getting repository')), ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); @@ -221,8 +220,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { mockSnapshotGetEsResponse, ]; - const response = await router.runRequest(mockRequest); - expect(response.status).toBe(500); + await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts index 65e941db88df6a..03e3b4ecc08870 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts @@ -61,7 +61,7 @@ export function registerSnapshotsRoutes({ body: e, }); } - return res.internalError({ body: e }); + throw e; } const snapshots: SnapshotDetails[] = []; @@ -176,7 +176,7 @@ export function registerSnapshotsRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); @@ -229,7 +229,7 @@ export function registerSnapshotsRoutes({ }); } // Case: default - return res.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/task_manager/server/routes/health.test.ts b/x-pack/plugins/task_manager/server/routes/health.test.ts index dd2a60bedcac6a..dd7ed69aaf27f9 100644 --- a/x-pack/plugins/task_manager/server/routes/health.test.ts +++ b/x-pack/plugins/task_manager/server/routes/health.test.ts @@ -114,7 +114,7 @@ describe('healthRoute', () => { const [, handler] = router.get.mock.calls[0]; - const [context, req, res] = mockHandlerArguments({}, {}, ['ok', 'internalError']); + const [context, req, res] = mockHandlerArguments({}, {}, ['ok']); await sleep(0); @@ -214,7 +214,7 @@ describe('healthRoute', () => { const [, handler] = router.get.mock.calls[0]; - const [context, req, res] = mockHandlerArguments({}, {}, ['ok', 'internalError']); + const [context, req, res] = mockHandlerArguments({}, {}, ['ok']); await sleep(2000); @@ -282,7 +282,7 @@ describe('healthRoute', () => { const [, handler] = router.get.mock.calls[0]; - const [context, req, res] = mockHandlerArguments({}, {}, ['ok', 'internalError']); + const [context, req, res] = mockHandlerArguments({}, {}, ['ok']); expect(await handler(context, req, res)).toMatchObject({ body: { diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts index c2bb27ce995ebe..a5da4741b10eb0 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts @@ -96,12 +96,12 @@ describe('cluster checkup API', () => { it('returns an 500 error if it throws', async () => { MigrationApis.getUpgradeAssistantStatus.mockRejectedValue(new Error(`scary error!`)); - const resp = await routeDependencies.router.getHandler({ - method: 'get', - pathPattern: '/api/upgrade_assistant/status', - })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); - - expect(resp.status).toEqual(500); + await expect( + routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory) + ).rejects.toThrow('scary error!'); }); }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts index be8d1739341825..b4dae6ec385b45 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts @@ -55,7 +55,7 @@ export function registerClusterCheckupRoutes({ cloud, router, licensing, log }: return response.forbidden(e.message); } - return response.internalError({ body: e }); + throw e; } } ) diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts index 0e0dd075624a68..0b595df0dc8c45 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts @@ -54,12 +54,12 @@ describe('deprecation logging API', () => { it('returns an error if it throws', async () => { (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster .getSettings as jest.Mock).mockRejectedValue(new Error(`scary error!`)); - const resp = await routeDependencies.router.getHandler({ - method: 'get', - pathPattern: '/api/upgrade_assistant/deprecation_logging', - })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); - - expect(resp.status).toEqual(500); + await expect( + routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/deprecation_logging', + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory) + ).rejects.toThrow('scary error!'); }); }); @@ -80,12 +80,12 @@ describe('deprecation logging API', () => { it('returns an error if it throws', async () => { (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster .putSettings as jest.Mock).mockRejectedValue(new Error(`scary error!`)); - const resp = await routeDependencies.router.getHandler({ - method: 'put', - pathPattern: '/api/upgrade_assistant/deprecation_logging', - })(routeHandlerContextMock, { body: { isEnabled: false } }, kibanaResponseFactory); - - expect(resp.status).toEqual(500); + await expect( + routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/deprecation_logging', + })(routeHandlerContextMock, { body: { isEnabled: false } }, kibanaResponseFactory) + ).rejects.toThrow('scary error!'); }); }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts index 298a87d962ff60..8b427c6443ac88 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts @@ -30,12 +30,8 @@ export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) request, response ) => { - try { - const result = await getDeprecationLoggingStatus(client); - return response.ok({ body: result }); - } catch (e) { - return response.internalError({ body: e }); - } + const result = await getDeprecationLoggingStatus(client); + return response.ok({ body: result }); } ) ); @@ -59,14 +55,10 @@ export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) request, response ) => { - try { - const { isEnabled } = request.body as { isEnabled: boolean }; - return response.ok({ - body: await setDeprecationLogging(client, isEnabled), - }); - } catch (e) { - return response.internalError({ body: e }); - } + const { isEnabled } = request.body as { isEnabled: boolean }; + return response.ok({ + body: await setDeprecationLogging(client, isEnabled), + }); } ) ); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts index aa8e42803121ca..d36f8225ffa3b2 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts @@ -66,7 +66,7 @@ const mapAnyErrorToKibanaHttpResponse = (e: any) => { return kibanaResponseFactory.notFound({ body: e.message }); case CannotCreateIndex: case ReindexTaskCannotBeDeleted: - return kibanaResponseFactory.internalError({ body: e.message }); + throw e; case ReindexTaskFailed: // Bad data return kibanaResponseFactory.customError({ body: e.message, statusCode: 422 }); @@ -78,7 +78,7 @@ const mapAnyErrorToKibanaHttpResponse = (e: any) => { // nothing matched } } - return kibanaResponseFactory.internalError({ body: e }); + throw e; }; export function registerReindexIndicesRoutes( diff --git a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts index 96c056ebe5ee8e..05ad542ec9c000 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts @@ -92,20 +92,20 @@ describe('Upgrade Assistant Telemetry API', () => { it('returns an error if it throws', async () => { (upsertUIOpenOption as jest.Mock).mockRejectedValue(new Error(`scary error!`)); - const resp = await routeDependencies.router.getHandler({ - method: 'put', - pathPattern: '/api/upgrade_assistant/stats/ui_open', - })( - routeHandlerContextMock, - createRequestMock({ - body: { - overview: false, - }, - }), - kibanaResponseFactory - ); - - expect(resp.status).toEqual(500); + await expect( + routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/stats/ui_open', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + overview: false, + }, + }), + kibanaResponseFactory + ) + ).rejects.toThrowError('scary error!'); }); }); @@ -118,7 +118,7 @@ describe('Upgrade Assistant Telemetry API', () => { stop: false, }; - (upsertUIReindexOption as jest.Mock).mockRejectedValue(returnPayload); + (upsertUIReindexOption as jest.Mock).mockResolvedValue(returnPayload); const resp = await routeDependencies.router.getHandler({ method: 'put', @@ -144,7 +144,7 @@ describe('Upgrade Assistant Telemetry API', () => { stop: true, }; - (upsertUIReindexOption as jest.Mock).mockRejectedValue(returnPayload); + (upsertUIReindexOption as jest.Mock).mockResolvedValue(returnPayload); const resp = await routeDependencies.router.getHandler({ method: 'put', @@ -168,20 +168,20 @@ describe('Upgrade Assistant Telemetry API', () => { it('returns an error if it throws', async () => { (upsertUIReindexOption as jest.Mock).mockRejectedValue(new Error(`scary error!`)); - const resp = await routeDependencies.router.getHandler({ - method: 'put', - pathPattern: '/api/upgrade_assistant/stats/ui_reindex', - })( - routeHandlerContextMock, - createRequestMock({ - body: { - start: false, - }, - }), - kibanaResponseFactory - ); - - expect(resp.status).toEqual(500); + await expect( + routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/stats/ui_reindex', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + start: false, + }, + }), + kibanaResponseFactory + ) + ).rejects.toThrowError('scary error!'); }); }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts index e7c8843c49750b..d24c384f7f0f35 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts @@ -24,18 +24,14 @@ export function registerTelemetryRoutes({ router, getSavedObjectsService }: Rout }, async (ctx, request, response) => { const { cluster, indices, overview } = request.body; - try { - return response.ok({ - body: await upsertUIOpenOption({ - savedObjects: getSavedObjectsService(), - cluster, - indices, - overview, - }), - }); - } catch (e) { - return response.internalError({ body: e }); - } + return response.ok({ + body: await upsertUIOpenOption({ + savedObjects: getSavedObjectsService(), + cluster, + indices, + overview, + }), + }); } ); @@ -53,19 +49,15 @@ export function registerTelemetryRoutes({ router, getSavedObjectsService }: Rout }, async (ctx, request, response) => { const { close, open, start, stop } = request.body; - try { - return response.ok({ - body: await upsertUIReindexOption({ - savedObjects: getSavedObjectsService(), - close, - open, - start, - stop, - }), - }); - } catch (e) { - return response.internalError({ body: e }); - } + return response.ok({ + body: await upsertUIReindexOption({ + savedObjects: getSavedObjectsService(), + close, + open, + start, + stop, + }), + }); } ); } diff --git a/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts b/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts index 632bf7423f8415..8b6add27f889a7 100644 --- a/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts +++ b/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts @@ -39,7 +39,7 @@ export const createRouteWithAuth = ( case 403: return response.forbidden({ body: { message } }); default: - return response.internalError(); + throw new Error('Failed to validate the license'); } }; diff --git a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts index 6b291a3be9dcde..24e501a1bddb83 100644 --- a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts +++ b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts @@ -26,34 +26,22 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({ esClient: esClient.asCurrentUser, }); - try { - const res = await uptimeRoute.handler({ - uptimeEsClient, - savedObjectsClient, - context, - request, - response, - }); - - if (res instanceof KibanaResponse) { - return res; - } - - return response.ok({ - body: { - ...res, - }, - }); - } catch (e) { - // please don't remove this, this will be really helpful during debugging - /* eslint-disable-next-line no-console */ - console.error(e); + const res = await uptimeRoute.handler({ + uptimeEsClient, + savedObjectsClient, + context, + request, + response, + }); - return response.internalError({ - body: { - message: e.message, - }, - }); + if (res instanceof KibanaResponse) { + return res; } + + return response.ok({ + body: { + ...res, + }, + }); }, }); diff --git a/x-pack/plugins/watcher/server/routes/api/indices/register_get_route.ts b/x-pack/plugins/watcher/server/routes/api/indices/register_get_route.ts index d4fb0016aada02..b234bed9f7d4dc 100644 --- a/x-pack/plugins/watcher/server/routes/api/indices/register_get_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/indices/register_get_route.ts @@ -86,7 +86,7 @@ export function registerGetRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/register_list_fields_route.ts b/x-pack/plugins/watcher/server/routes/api/register_list_fields_route.ts index f6803767d89ff1..0882fc3a65027a 100644 --- a/x-pack/plugins/watcher/server/routes/api/register_list_fields_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/register_list_fields_route.ts @@ -57,7 +57,7 @@ export function registerListFieldsRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/register_load_history_route.ts b/x-pack/plugins/watcher/server/routes/api/register_load_history_route.ts index 8ab40b346e9794..629f29734c6031 100644 --- a/x-pack/plugins/watcher/server/routes/api/register_load_history_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/register_load_history_route.ts @@ -69,7 +69,7 @@ export function registerLoadHistoryRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/settings/register_load_route.ts b/x-pack/plugins/watcher/server/routes/api/settings/register_load_route.ts index 5e289edecefffd..ef5c7c6177ce97 100644 --- a/x-pack/plugins/watcher/server/routes/api/settings/register_load_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/settings/register_load_route.ts @@ -36,7 +36,7 @@ export function registerLoadRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.ts index f7ef97c151b2fb..1afec0ada91043 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.ts @@ -61,7 +61,7 @@ export function registerAcknowledgeRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_activate_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_activate_route.ts index 6d0a2a70850255..85d1d0c51f0b3f 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_activate_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_activate_route.ts @@ -57,7 +57,7 @@ export function registerActivateRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_deactivate_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_deactivate_route.ts index 89497bd092f320..071c9d17beee17 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_deactivate_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_deactivate_route.ts @@ -57,7 +57,7 @@ export function registerDeactivateRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_delete_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_delete_route.ts index c1e36f5d9c62ff..ebf5b41bc589c4 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_delete_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_delete_route.ts @@ -44,7 +44,7 @@ export function registerDeleteRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_execute_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_execute_route.ts index f8eb3df6126303..e2078ac5cc1d94 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_execute_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_execute_route.ts @@ -74,7 +74,7 @@ export function registerExecuteRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_history_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_history_route.ts index ac19d0f71e31c4..cafcf81511a4fa 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_history_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_history_route.ts @@ -94,7 +94,7 @@ export function registerHistoryRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_load_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_load_route.ts index 350831d32814cf..bba60cf93054cc 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_load_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_load_route.ts @@ -61,7 +61,7 @@ export function registerLoadRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_save_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_save_route.ts index 9b5d3b4615c07d..b4a219979e6502 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_save_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_save_route.ts @@ -62,7 +62,7 @@ export function registerSaveRoute(deps: RouteDependencies) { } catch (e) { const es404 = isEsError(e) && e.statusCode === 404; if (!es404) { - return response.internalError({ body: e }); + throw e; } // Else continue... } @@ -97,7 +97,7 @@ export function registerSaveRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts b/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts index 3b2050bff15b59..0310d7eed9d344 100644 --- a/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watch/register_visualize_route.ts @@ -62,7 +62,7 @@ export function registerVisualizeRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.ts b/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.ts index fad293715b9bb8..631f6fdcb0903d 100644 --- a/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.ts @@ -51,12 +51,8 @@ export function registerDeleteRoute(deps: RouteDependencies) { }, }, licensePreRoutingFactory(deps, async (ctx, request, response) => { - try { - const results = await deleteWatches(ctx.watcher!.client, request.body.watchIds); - return response.ok({ body: { results } }); - } catch (e) { - return response.internalError({ body: e }); - } + const results = await deleteWatches(ctx.watcher!.client, request.body.watchIds); + return response.ok({ body: { results } }); }) ); } diff --git a/x-pack/plugins/watcher/server/routes/api/watches/register_list_route.ts b/x-pack/plugins/watcher/server/routes/api/watches/register_list_route.ts index f1119219dae3cd..6a4e85800fa8d5 100644 --- a/x-pack/plugins/watcher/server/routes/api/watches/register_list_route.ts +++ b/x-pack/plugins/watcher/server/routes/api/watches/register_list_route.ts @@ -75,7 +75,7 @@ export function registerListRoute(deps: RouteDependencies) { } // Case: default - return response.internalError({ body: e }); + throw e; } }) ); diff --git a/x-pack/plugins/xpack_legacy/server/routes/settings.ts b/x-pack/plugins/xpack_legacy/server/routes/settings.ts index b29c52cd011c48..9117637b70bee6 100644 --- a/x-pack/plugins/xpack_legacy/server/routes/settings.ts +++ b/x-pack/plugins/xpack_legacy/server/routes/settings.ts @@ -53,7 +53,7 @@ export function registerSettingsRoute({ | KibanaSettingsCollector | undefined; if (!settingsCollector) { - return res.internalError(); + throw new Error('The settings collector is not registered'); } const settings = diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts index 18e813d30659b1..778c87f0ed03ec 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts @@ -42,23 +42,19 @@ export class FixturePlugin implements Plugin, res: KibanaResponseFactory ): Promise> { - try { - let namespace: string | undefined; - if (spaces && req.body.spaceId) { - namespace = spaces.spacesService.spaceIdToNamespace(req.body.spaceId); - } - const [, { encryptedSavedObjects }] = await core.getStartServices(); - await encryptedSavedObjects - .getClient({ - includedHiddenTypes: ['alert', 'action'], - }) - .getDecryptedAsInternalUser(req.body.type, req.body.id, { - namespace, - }); - return res.ok({ body: { success: true } }); - } catch (err) { - return res.internalError({ body: err }); + let namespace: string | undefined; + if (spaces && req.body.spaceId) { + namespace = spaces.spacesService.spaceIdToNamespace(req.body.spaceId); } + const [, { encryptedSavedObjects }] = await core.getStartServices(); + await encryptedSavedObjects + .getClient({ + includedHiddenTypes: ['alert', 'action'], + }) + .getDecryptedAsInternalUser(req.body.type, req.body.id, { + namespace, + }); + return res.ok({ body: { success: true } }); } ); } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts index 7eacc9ba6f0cb3..d9e362a99e6488 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts @@ -66,7 +66,7 @@ export function defineRoutes(core: CoreSetup) { const user = await security.authc.getCurrentUser(req); if (!user) { - return res.internalError({}); + throw new Error('Failed to get the current user'); } // Create an API key using the new grant API - in this case the Kibana system user is creating the @@ -78,7 +78,7 @@ export function defineRoutes(core: CoreSetup) { }); if (!createAPIKeyResult) { - return res.internalError({}); + throw new Error('Failed to grant an API Key'); } const result = await savedObjectsWithAlerts.update( diff --git a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts index 57beb40b164592..7213beb2b49a54 100644 --- a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts +++ b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/init_routes.ts @@ -71,20 +71,16 @@ export function initRoutes( req: KibanaRequest, res: KibanaResponseFactory ): Promise> { - try { - const taskManager = await taskManagerStart; - const { task: taskFields } = req.body; - const task = { - ...taskFields, - scope: [scope], - }; + const taskManager = await taskManagerStart; + const { task: taskFields } = req.body; + const task = { + ...taskFields, + scope: [scope], + }; - const taskResult = await taskManager.schedule(task, { req }); + const taskResult = await taskManager.schedule(task, { req }); - return res.ok({ body: taskResult }); - } catch (err) { - return res.internalError({ body: err }); - } + return res.ok({ body: taskResult }); } ); From 619a65822766282d7ecb50c106eeb6359b34d44b Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 18 Feb 2021 17:33:16 +0000 Subject: [PATCH 49/93] chore(NA): setup renovate for the new 7.13 (#91859) --- renovate.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/renovate.json5 b/renovate.json5 index 52d7a06c88339a..f8fa3b916946f8 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -38,7 +38,7 @@ packageNames: ['@elastic/charts'], reviewers: ['markov00'], matchBaseBranches: ['master'], - labels: ['release_note:skip', 'v8.0.0', 'v7.12.0'], + labels: ['release_note:skip', 'v8.0.0', 'v7.13.0'], enabled: true, }, { @@ -54,7 +54,7 @@ packageNames: ['@elastic/elasticsearch'], reviewers: ['team:kibana-operations'], matchBaseBranches: ['7.x'], - labels: ['release_note:skip', 'v7.12.0', 'Team:Operations', 'backport:skip'], + labels: ['release_note:skip', 'v7.13.0', 'Team:Operations', 'backport:skip'], enabled: true, }, { From 60e63aa53b55e68af6be31e7a44a0f94f5c2c4e0 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Thu, 18 Feb 2021 17:40:25 +0000 Subject: [PATCH 50/93] [ML] Switching to new find file structure endpoint (#91802) * [ML] Switching to new find file structure endpoint * js client change --- package.json | 2 +- x-pack/plugins/ml/server/lib/ml_client/ml_client.ts | 3 --- x-pack/plugins/ml/server/lib/ml_client/types.ts | 1 - .../file_data_visualizer/file_data_visualizer.ts | 8 +++++--- .../plugins/ml/server/routes/file_data_visualizer.ts | 10 +++++----- yarn.lock | 8 ++++---- 6 files changed, 15 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 96db1977237d1c..a65c12fce46990 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "@babel/core": "^7.12.10", "@babel/runtime": "^7.12.5", "@elastic/datemath": "link:packages/elastic-datemath", - "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary", + "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.2", "@elastic/ems-client": "7.12.0", "@elastic/eui": "31.7.0", "@elastic/filesaver": "1.1.2", diff --git a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts index d075f0cc4e6605..278dd19f74acc3 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts @@ -196,9 +196,6 @@ export function getMlClient( await jobIdsCheck('data-frame-analytics', p); return mlClient.explainDataFrameAnalytics(...p); }, - async findFileStructure(...p: Parameters) { - return mlClient.findFileStructure(...p); - }, async flushJob(...p: Parameters) { await jobIdsCheck('anomaly-detector', p); return mlClient.flushJob(...p); diff --git a/x-pack/plugins/ml/server/lib/ml_client/types.ts b/x-pack/plugins/ml/server/lib/ml_client/types.ts index 6618e19dfe2663..7ff1acf4ac0ce2 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/types.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/types.ts @@ -29,7 +29,6 @@ export type MlClientParams = | Parameters | Parameters | Parameters - | Parameters | Parameters | Parameters | Parameters diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts index 2dd3373f2e42f5..6e57e997e5f008 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts @@ -5,20 +5,22 @@ * 2.0. */ +import { IScopedClusterClient } from 'kibana/server'; import { AnalysisResult, FormattedOverrides, InputOverrides, FindFileStructureResponse, } from '../../../common/types/file_datavisualizer'; -import type { MlClient } from '../../lib/ml_client'; export type InputData = any[]; -export function fileDataVisualizerProvider(mlClient: MlClient) { +export function fileDataVisualizerProvider(client: IScopedClusterClient) { async function analyzeFile(data: InputData, overrides: InputOverrides): Promise { overrides.explain = overrides.explain === undefined ? 'true' : overrides.explain; - const { body } = await mlClient.findFileStructure({ + const { + body, + } = await client.asInternalUser.textStructure.findStructure({ body: data, ...overrides, }); diff --git a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts index fa56a6854de62d..6b200c59f57d5a 100644 --- a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { IScopedClusterClient } from 'kibana/server'; import { schema } from '@kbn/config-schema'; import { MAX_FILE_SIZE_BYTES } from '../../../file_upload/common'; import { InputOverrides } from '../../common/types/file_datavisualizer'; @@ -13,10 +14,9 @@ import { InputData, fileDataVisualizerProvider } from '../models/file_data_visua import { RouteInitialization } from '../types'; import { analyzeFileQuerySchema } from './schemas/file_data_visualizer_schema'; -import type { MlClient } from '../lib/ml_client'; -function analyzeFiles(mlClient: MlClient, data: InputData, overrides: InputOverrides) { - const { analyzeFile } = fileDataVisualizerProvider(mlClient); +function analyzeFiles(client: IScopedClusterClient, data: InputData, overrides: InputOverrides) { + const { analyzeFile } = fileDataVisualizerProvider(client); return analyzeFile(data, overrides); } @@ -48,9 +48,9 @@ export function fileDataVisualizerRoutes({ router, routeGuard }: RouteInitializa tags: ['access:ml:canFindFileStructure'], }, }, - routeGuard.basicLicenseAPIGuard(async ({ mlClient, request, response }) => { + routeGuard.basicLicenseAPIGuard(async ({ client, request, response }) => { try { - const result = await analyzeFiles(mlClient, request.body, request.query); + const result = await analyzeFiles(client, request.body, request.query); return response.ok({ body: result }); } catch (e) { return response.customError(wrapError(e)); diff --git a/yarn.lock b/yarn.lock index 836c067c73324a..4738925e44dfbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2167,10 +2167,10 @@ version "0.0.0" uid "" -"@elastic/elasticsearch@npm:@elastic/elasticsearch-canary@^8.0.0-canary": - version "8.0.0-canary.1" - resolved "https://registry.yarnpkg.com/@elastic/elasticsearch-canary/-/elasticsearch-canary-8.0.0-canary.1.tgz#5cd0eda62531b71af66a08da6c3cebc26a73d4c0" - integrity sha512-VhQ42wH+0OGmHSlc4It3bqGTL7mLuC2RIionJZBIuY5P6lwUMz7goelfyfTHoo+LStxz5QQ8Zt2xcnAnShTBJg== +"@elastic/elasticsearch@npm:@elastic/elasticsearch-canary@^8.0.0-canary.2": + version "8.0.0-canary.2" + resolved "https://registry.yarnpkg.com/@elastic/elasticsearch-canary/-/elasticsearch-canary-8.0.0-canary.2.tgz#476e22bc90fc4f422f7195f693fdcddb7f8e1897" + integrity sha512-xYdVJ1MCAprVxd0rqmkBVof7I0N+e6VBCcr0UOwEYjvpQJTvu6PPQROBAAmtAAgvIKs4a8HmpArGgu5QJUnNjw== dependencies: debug "^4.1.1" hpagent "^0.1.1" From 18db413083d7f737e60221df6ae3219daad96a47 Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Thu, 18 Feb 2021 12:43:24 -0500 Subject: [PATCH 51/93] Adding automated a11y tests for Canvas (#91571) --- x-pack/plugins/canvas/i18n/components.ts | 6 ++ .../confirm_modal/confirm_modal.tsx | 1 + .../workpad_loader/workpad_loader.js | 4 +- .../workpad_manager/workpad_manager.js | 5 +- .../workpad_templates.stories.storyshot | 1 + .../workpad_templates/workpad_templates.tsx | 1 + x-pack/test/accessibility/apps/canvas.ts | 60 +++++++++++++++++++ x-pack/test/accessibility/config.ts | 1 + 8 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 x-pack/test/accessibility/apps/canvas.ts diff --git a/x-pack/plugins/canvas/i18n/components.ts b/x-pack/plugins/canvas/i18n/components.ts index 88ea8700bd464a..afd3d1408e1f1a 100644 --- a/x-pack/plugins/canvas/i18n/components.ts +++ b/x-pack/plugins/canvas/i18n/components.ts @@ -1752,6 +1752,12 @@ export const ComponentStrings = { description: 'This column in the table contains the date/time the workpad was last updated.', }), + getTableActionsColumnTitle: () => + i18n.translate('xpack.canvas.workpadLoader.table.actionsColumnTitle', { + defaultMessage: 'Actions', + description: + 'This column in the table contains the actions that can be taken on a workpad.', + }), }, WorkpadManager: { getModalTitle: () => diff --git a/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx b/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx index 521ced0d731f2c..3156b14f209f1a 100644 --- a/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx +++ b/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx @@ -49,6 +49,7 @@ export const ConfirmModal: FunctionComponent = (props) => { cancelButtonText={cancelButtonText} defaultFocusedButton="confirm" buttonColor="danger" + data-test-subj="canvasConfirmModal" > {message} diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js index 5a33b25399f77e..25c17fabe9fad8 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -213,7 +213,7 @@ export class WorkpadLoader extends React.PureComponent { width: '20%', render: (date) => this.props.formatDate(date), }, - { name: '', actions, width: '5%' }, + { name: strings.getTableActionsColumnTitle(), actions, width: '100px' }, ]; const sorting = { @@ -310,6 +310,7 @@ export class WorkpadLoader extends React.PureComponent { onClick={this.openRemoveConfirm} disabled={!canUserWrite} aria-label={strings.getDeleteButtonAriaLabel(selectedWorkpads.length)} + data-test-subj="deleteWorkpadButton" > {strings.getDeleteButtonLabel(selectedWorkpads.length)}
@@ -331,6 +332,7 @@ export class WorkpadLoader extends React.PureComponent { display="default" compressed className="canvasWorkpad__upload--compressed" + aria-label={strings.getFilePickerPlaceholder()} initialPromptText={strings.getFilePickerPlaceholder()} onChange={([file]) => uploadWorkpad(file, this.onUpload, this.props.notify)} accept="application/json" diff --git a/x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js b/x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js index 3f128a1758c3f9..8055be32ac481a 100644 --- a/x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js +++ b/x-pack/plugins/canvas/public/components/workpad_manager/workpad_manager.js @@ -37,6 +37,7 @@ export const WorkpadManager = ({ onClose }) => { { id: 'workpadTemplates', name: strings.getWorkpadTemplatesTabLabel(), + 'data-test-subj': 'workpadTemplates', content: ( @@ -50,7 +51,9 @@ export const WorkpadManager = ({ onClose }) => { - {strings.getModalTitle()} + +

{strings.getModalTitle()}

+
diff --git a/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot index 489827246e998a..e984aae3356366 100644 --- a/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/workpad_templates/examples/__snapshots__/workpad_templates.stories.storyshot @@ -95,6 +95,7 @@ exports[`Storyshots components/WorkpadTemplates default 1`] = ` />
{rows.length > 0 && ( diff --git a/x-pack/test/accessibility/apps/canvas.ts b/x-pack/test/accessibility/apps/canvas.ts new file mode 100644 index 00000000000000..c802d62b05bf94 --- /dev/null +++ b/x-pack/test/accessibility/apps/canvas.ts @@ -0,0 +1,60 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const a11y = getService('a11y'); + const testSubjects = getService('testSubjects'); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const { common } = getPageObjects(['common']); + + describe('Canvas', () => { + before(async () => { + await esArchiver.load('canvas/default'); + await common.navigateToApp('canvas'); + }); + + it('loads workpads', async function () { + await retry.waitFor( + 'canvas workpads visible', + async () => await testSubjects.exists('canvasWorkpadLoaderTable') + ); + await a11y.testAppSnapshot(); + }); + + it('provides bulk actions', async function () { + await testSubjects.click('checkboxSelectAll'); + await retry.waitFor( + 'canvas bulk actions visible', + async () => await testSubjects.exists('deleteWorkpadButton') + ); + await a11y.testAppSnapshot(); + }); + + it('can delete workpads', async function () { + await testSubjects.click('deleteWorkpadButton'); + await retry.waitFor( + 'canvas delete modal visible', + async () => await testSubjects.exists('canvasConfirmModal') + ); + await a11y.testAppSnapshot(); + }); + + it('can navigate to templates', async function () { + await testSubjects.click('confirmModalCancelButton'); // close modal from previous test + + await testSubjects.click('workpadTemplates'); + await retry.waitFor( + 'canvas templates visible', + async () => await testSubjects.exists('canvasTemplatesTable') + ); + await a11y.testAppSnapshot(); + }); + }); +} diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts index bfd12c4eee6e7d..b014f672ed5a64 100644 --- a/x-pack/test/accessibility/config.ts +++ b/x-pack/test/accessibility/config.ts @@ -32,6 +32,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./apps/ml'), require.resolve('./apps/lens'), require.resolve('./apps/upgrade_assistant'), + require.resolve('./apps/canvas'), ], pageObjects, From df8f2b1412f09fd16bea0ea3f3ee34fcf9d8afd6 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Thu, 18 Feb 2021 10:48:42 -0700 Subject: [PATCH 52/93] [Maps] Fix issue preventing WebGL warning message from appearing (#91069) --- x-pack/plugins/maps/public/reducers/store.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/x-pack/plugins/maps/public/reducers/store.js b/x-pack/plugins/maps/public/reducers/store.js index 76199de5b24c92..f1020b2d6cdb69 100644 --- a/x-pack/plugins/maps/public/reducers/store.js +++ b/x-pack/plugins/maps/public/reducers/store.js @@ -10,7 +10,6 @@ import thunk from 'redux-thunk'; import { ui, DEFAULT_MAP_UI_STATE } from './ui'; import { map, DEFAULT_MAP_STATE } from './map'; // eslint-disable-line import/named import { nonSerializableInstances } from './non_serializable_instances'; -import { MAP_DESTROYED } from '../actions'; export const DEFAULT_MAP_STORE_STATE = { ui: { ...DEFAULT_MAP_UI_STATE }, @@ -26,16 +25,7 @@ export function createMapStore() { nonSerializableInstances, }); - const rootReducer = (state, action) => { - // Reset store on map destroyed - if (action.type === MAP_DESTROYED) { - state = undefined; - } - - return combinedReducers(state, action); - }; - const storeConfig = {}; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; - return createStore(rootReducer, storeConfig, composeEnhancers(...enhancers)); + return createStore(combinedReducers, storeConfig, composeEnhancers(...enhancers)); } From eddf1c94b1c76492e619bef7557a04e8cbbea7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Thu, 18 Feb 2021 18:00:43 +0000 Subject: [PATCH 53/93] Index pattern field editor (#88995) Index pattern field editor --- .i18nrc.json | 1 + docs/developer/plugin-list.asciidoc | 4 + .../src/painless/diagnostics_adapter.ts | 13 + packages/kbn-monaco/src/painless/index.ts | 10 +- packages/kbn-monaco/src/painless/language.ts | 11 +- packages/kbn-optimizer/limits.yml | 3 +- .../public/doc_links/doc_links_service.ts | 1 + .../data/common/index_patterns/constants.ts | 9 + .../index_pattern_field.test.ts.snap | 4 +- .../fields/index_pattern_field.test.ts | 4 +- .../fields/index_pattern_field.ts | 8 +- .../data/common/index_patterns/index.ts | 1 + .../__snapshots__/index_pattern.test.ts.snap | 6 +- .../index_patterns/index_pattern.ts | 3 +- .../index_patterns/index_patterns.ts | 7 +- .../data/common/index_patterns/types.ts | 7 +- .../forms/hook_form_lib/hooks/use_form.ts | 3 +- .../index_pattern_field_editor/README.md | 69 +++++ .../index_pattern_field_editor/jest.config.js | 13 + .../index_pattern_field_editor/kibana.json | 9 + .../public/assets/icons/LICENSE.txt | 20 ++ .../public/assets/icons/cv.png | Bin 0 -> 802 bytes .../public/assets/icons/de.png | Bin 0 -> 124 bytes .../public/assets/icons/go.png | Bin 0 -> 1938 bytes .../public/assets/icons/ne.png | Bin 0 -> 336 bytes .../public/assets/icons/ni.png | Bin 0 -> 919 bytes .../public/assets/icons/stop.png | Bin 0 -> 1912 bytes .../public/assets/icons/us.png | Bin 0 -> 1074 bytes .../delete_field_provider.tsx | 129 ++++++++ .../get_delete_provider.tsx | 62 ++++ .../components/delete_field_provider/index.ts | 9 + .../advanced_parameters_section.tsx | 44 +++ .../components/field_editor/constants.ts | 37 +++ .../field_editor/field_editor.test.tsx | 212 +++++++++++++ .../components/field_editor/field_editor.tsx | 286 ++++++++++++++++++ .../form_fields/custom_label_field.tsx | 15 + .../field_editor/form_fields/format_field.tsx | 82 +++++ .../field_editor/form_fields/index.ts | 17 ++ .../form_fields/popularity_field.tsx | 21 ++ .../field_editor/form_fields/script_field.tsx | 227 ++++++++++++++ .../field_editor/form_fields/type_field.tsx | 65 ++++ .../components/field_editor/form_row.tsx | 86 ++++++ .../components/field_editor/form_schema.ts | 119 ++++++++ .../public/components/field_editor/index.ts | 9 + .../public/components/field_editor/lib.ts | 60 ++++ .../field_editor/shadowing_field_warning.tsx | 32 ++ .../field_editor_flyout_content.test.ts | 198 ++++++++++++ .../field_editor_flyout_content.tsx | 253 ++++++++++++++++ .../field_editor_flyout_content_container.tsx | 205 +++++++++++++ .../__snapshots__/format_editor.test.tsx.snap | 25 ++ .../bytes/__snapshots__/bytes.test.tsx.snap | 4 +- .../editors/bytes/bytes.test.tsx | 0 .../editors/bytes/bytes.ts | 0 .../editors/bytes/index.ts | 0 .../color/__snapshots__/color.test.tsx.snap | 30 +- .../editors/color/color.test.tsx | 2 +- .../editors/color/color.tsx | 20 +- .../editors/color/index.ts | 0 .../date/__snapshots__/date.test.tsx.snap | 4 +- .../editors/date/date.test.tsx | 0 .../field_format_editor/editors/date/date.tsx | 4 +- .../field_format_editor/editors/date/index.ts | 0 .../__snapshots__/date_nanos.test.tsx.snap | 4 +- .../editors/date_nanos/date_nanos.test.tsx | 2 +- .../editors/date_nanos/date_nanos.tsx | 4 +- .../editors/date_nanos/index.ts | 0 .../__snapshots__/default.test.tsx.snap | 0 .../editors/default/default.test.tsx | 0 .../editors/default/default.tsx | 8 +- .../editors/default/index.ts | 0 .../__snapshots__/duration.test.tsx.snap | 12 +- .../editors/duration/duration.test.tsx | 0 .../editors/duration/duration.tsx | 10 +- .../editors/duration/index.tsx | 0 .../field_format_editor/editors/index.ts | 0 .../number/__snapshots__/number.test.tsx.snap | 4 +- .../editors/number/index.ts | 0 .../editors/number/number.test.tsx | 0 .../editors/number/number.tsx | 4 +- .../__snapshots__/percent.test.tsx.snap | 4 +- .../editors/percent/index.ts | 0 .../editors/percent/percent.test.tsx | 2 +- .../editors/percent/percent.tsx | 0 .../__snapshots__/static_lookup.test.tsx.snap | 16 +- .../editors/static_lookup/index.ts | 0 .../static_lookup/static_lookup.test.tsx | 2 +- .../editors/static_lookup/static_lookup.tsx | 16 +- .../string/__snapshots__/string.test.tsx.snap | 2 +- .../editors/string/index.ts | 0 .../editors/string/string.test.tsx | 0 .../editors/string/string.tsx | 2 +- .../__snapshots__/truncate.test.tsx.snap | 2 +- .../editors/truncate/index.ts | 0 .../editors/truncate/sample.ts | 0 .../editors/truncate/truncate.test.tsx | 0 .../editors/truncate/truncate.tsx | 2 +- .../url/__snapshots__/url.test.tsx.snap | 36 ++- .../field_format_editor/editors/url/index.ts | 0 .../editors/url/url.test.tsx | 36 +-- .../field_format_editor/editors/url/url.tsx | 79 ++--- .../field_format_editor.tsx | 186 ++++++++++++ .../format_editor.test.tsx | 63 ++++ .../field_format_editor/format_editor.tsx | 67 ++++ .../components/field_format_editor/index.ts | 10 + .../__snapshots__/samples.test.tsx.snap | 2 +- .../field_format_editor/samples/index.ts | 0 .../field_format_editor/samples/samples.scss | 0 .../samples/samples.test.tsx | 0 .../field_format_editor/samples/samples.tsx | 8 +- .../components/field_format_editor/types.ts | 14 + .../public/components/index.ts | 20 ++ .../public/constants.ts | 9 + .../public/index.ts | 32 ++ .../public/lib/documentation.ts | 21 ++ .../public/lib/index.ts | 13 + .../lib/runtime_field_validation.test.ts | 165 ++++++++++ .../public/lib/runtime_field_validation.ts | 116 +++++++ .../public/lib/serialization.ts | 28 ++ .../public/mocks.ts | 46 +++ .../public/open_editor.tsx | 119 ++++++++ .../public/plugin.test.tsx | 114 +++++++ .../public/plugin.ts | 60 ++++ .../field_format_editors.ts | 2 +- .../service/field_format_editors/index.ts | 0 .../public/service/format_editor_service.ts | 72 +++++ .../public/service/index.ts | 9 + .../public/shared_imports.ts | 36 +++ .../public/test_utils/helpers.ts | 27 ++ .../public/test_utils/index.ts | 13 + .../public/test_utils/mocks.ts | 24 ++ .../public/test_utils/setup_environment.tsx | 80 +++++ .../public/test_utils/test_utils.ts | 11 + .../public/types.ts | 63 ++++ .../index_pattern_field_editor/tsconfig.json | 20 ++ .../index_pattern_management/kibana.json | 2 +- .../create_index_pattern_wizard.test.tsx.snap | 10 + .../edit_index_pattern/edit_index_pattern.tsx | 19 +- .../indexed_fields_table.test.tsx.snap | 35 ++- .../table/__snapshots__/table.test.tsx.snap | 32 +- .../components/table/table.test.tsx | 65 ++-- .../components/table/table.tsx | 41 ++- .../indexed_fields_table.test.tsx | 13 +- .../indexed_fields_table.tsx | 9 +- .../indexed_fields_table/types.ts | 2 + .../edit_index_pattern/tabs/tabs.tsx | 111 +++++-- .../label_template_flyout.test.tsx.snap | 109 ------- .../url_template_flyout.test.tsx.snap | 114 ------- .../url/label_template_flyout.test.tsx | 24 -- .../editors/url/label_template_flyout.tsx | 142 --------- .../editors/url/url_template_flyout.test.tsx | 24 -- .../editors/url/url_template_flyout.tsx | 112 ------- .../field_format_editor.test.tsx | 2 +- .../field_format_editor.tsx | 2 +- .../components/field_format_editor/index.ts | 1 - .../components/field_editor/field_editor.tsx | 4 +- .../index_pattern_management/public/index.ts | 2 - .../mount_management_section.tsx | 4 +- .../index_pattern_management/public/mocks.ts | 23 +- .../index_pattern_management/public/plugin.ts | 2 + .../index_pattern_management_service.ts | 36 --- .../index_pattern_management/public/types.ts | 3 + .../index_pattern_management/tsconfig.json | 2 + .../management/_handle_version_conflict.js | 6 + .../apps/management/_index_pattern_filter.js | 8 +- .../management/_index_pattern_popularity.js | 8 +- .../management/_index_pattern_results_sort.js | 32 +- test/functional/apps/visualize/_tag_cloud.ts | 6 + test/functional/page_objects/settings_page.ts | 2 +- tsconfig.json | 1 + .../runtime_field_form/runtime_field_form.tsx | 2 +- .../translations/translations/ja-JP.json | 110 +++---- .../translations/translations/zh-CN.json | 110 +++---- .../apps/rollup_job/hybrid_index_pattern.js | 2 +- 173 files changed, 4358 insertions(+), 1034 deletions(-) create mode 100644 src/plugins/data/common/index_patterns/constants.ts create mode 100644 src/plugins/index_pattern_field_editor/README.md create mode 100644 src/plugins/index_pattern_field_editor/jest.config.js create mode 100644 src/plugins/index_pattern_field_editor/kibana.json create mode 100644 src/plugins/index_pattern_field_editor/public/assets/icons/LICENSE.txt create mode 100644 src/plugins/index_pattern_field_editor/public/assets/icons/cv.png create mode 100644 src/plugins/index_pattern_field_editor/public/assets/icons/de.png create mode 100644 src/plugins/index_pattern_field_editor/public/assets/icons/go.png create mode 100644 src/plugins/index_pattern_field_editor/public/assets/icons/ne.png create mode 100644 src/plugins/index_pattern_field_editor/public/assets/icons/ni.png create mode 100644 src/plugins/index_pattern_field_editor/public/assets/icons/stop.png create mode 100644 src/plugins/index_pattern_field_editor/public/assets/icons/us.png create mode 100644 src/plugins/index_pattern_field_editor/public/components/delete_field_provider/delete_field_provider.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/delete_field_provider/get_delete_provider.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/delete_field_provider/index.ts create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/advanced_parameters_section.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/constants.ts create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.test.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/custom_label_field.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/format_field.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/index.ts create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/popularity_field.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/script_field.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/type_field.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/form_row.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/form_schema.ts create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/index.ts create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/lib.ts create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor/shadowing_field_warning.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content_container.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_format_editor/__snapshots__/format_editor.test.tsx.snap rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap (92%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/bytes/bytes.test.tsx (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/bytes/bytes.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/bytes/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/color/__snapshots__/color.test.tsx.snap (87%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/color/color.test.tsx (96%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/color/color.tsx (89%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/color/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/date/__snapshots__/date.test.tsx.snap (93%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/date/date.test.tsx (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/date/date.tsx (94%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/date/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/date_nanos/__snapshots__/date_nanos.test.tsx.snap (93%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/date_nanos/date_nanos.test.tsx (95%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/date_nanos/date_nanos.tsx (94%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/date_nanos/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/default/__snapshots__/default.test.tsx.snap (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/default/default.test.tsx (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/default/default.tsx (92%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/default/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/duration/__snapshots__/duration.test.tsx.snap (93%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/duration/duration.test.tsx (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/duration/duration.tsx (93%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/duration/index.tsx (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap (92%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/number/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/number/number.test.tsx (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/number/number.tsx (94%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/percent/__snapshots__/percent.test.tsx.snap (92%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/percent/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/percent/percent.test.tsx (95%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/percent/percent.tsx (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/static_lookup/__snapshots__/static_lookup.test.tsx.snap (88%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/static_lookup/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/static_lookup/static_lookup.test.tsx (96%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/static_lookup/static_lookup.tsx (88%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/string/__snapshots__/string.test.tsx.snap (96%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/string/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/string/string.test.tsx (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/string/string.tsx (96%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/truncate/__snapshots__/truncate.test.tsx.snap (96%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/truncate/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/truncate/sample.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/truncate/truncate.test.tsx (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/truncate/truncate.tsx (96%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap (92%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/url/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/url/url.test.tsx (75%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/editors/url/url.tsx (74%) create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_format_editor/field_format_editor.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_format_editor/format_editor.test.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_format_editor/format_editor.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_format_editor/index.ts rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/samples/__snapshots__/samples.test.tsx.snap (96%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/samples/index.ts (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/samples/samples.scss (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/samples/samples.test.tsx (100%) rename src/plugins/{index_pattern_management/public/components/field_editor => index_pattern_field_editor/public}/components/field_format_editor/samples/samples.tsx (87%) create mode 100644 src/plugins/index_pattern_field_editor/public/components/field_format_editor/types.ts create mode 100644 src/plugins/index_pattern_field_editor/public/components/index.ts create mode 100644 src/plugins/index_pattern_field_editor/public/constants.ts create mode 100644 src/plugins/index_pattern_field_editor/public/index.ts create mode 100644 src/plugins/index_pattern_field_editor/public/lib/documentation.ts create mode 100644 src/plugins/index_pattern_field_editor/public/lib/index.ts create mode 100644 src/plugins/index_pattern_field_editor/public/lib/runtime_field_validation.test.ts create mode 100644 src/plugins/index_pattern_field_editor/public/lib/runtime_field_validation.ts create mode 100644 src/plugins/index_pattern_field_editor/public/lib/serialization.ts create mode 100644 src/plugins/index_pattern_field_editor/public/mocks.ts create mode 100644 src/plugins/index_pattern_field_editor/public/open_editor.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/plugin.test.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/plugin.ts rename src/plugins/{index_pattern_management => index_pattern_field_editor}/public/service/field_format_editors/field_format_editors.ts (89%) rename src/plugins/{index_pattern_management => index_pattern_field_editor}/public/service/field_format_editors/index.ts (100%) create mode 100644 src/plugins/index_pattern_field_editor/public/service/format_editor_service.ts create mode 100644 src/plugins/index_pattern_field_editor/public/service/index.ts create mode 100644 src/plugins/index_pattern_field_editor/public/shared_imports.ts create mode 100644 src/plugins/index_pattern_field_editor/public/test_utils/helpers.ts create mode 100644 src/plugins/index_pattern_field_editor/public/test_utils/index.ts create mode 100644 src/plugins/index_pattern_field_editor/public/test_utils/mocks.ts create mode 100644 src/plugins/index_pattern_field_editor/public/test_utils/setup_environment.tsx create mode 100644 src/plugins/index_pattern_field_editor/public/test_utils/test_utils.ts create mode 100644 src/plugins/index_pattern_field_editor/public/types.ts create mode 100644 src/plugins/index_pattern_field_editor/tsconfig.json delete mode 100644 src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/label_template_flyout.test.tsx.snap delete mode 100644 src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.tsx.snap delete mode 100644 src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.test.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.test.tsx delete mode 100644 src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.tsx diff --git a/.i18nrc.json b/.i18nrc.json index 0cdcae08e54e0a..efbb5ecc0194e9 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -29,6 +29,7 @@ "maps_legacy": "src/plugins/maps_legacy", "monaco": "packages/kbn-monaco/src", "presentationUtil": "src/plugins/presentation_util", + "indexPatternFieldEditor": "src/plugins/index_pattern_field_editor", "indexPatternManagement": "src/plugins/index_pattern_management", "advancedSettings": "src/plugins/advanced_settings", "kibana_legacy": "src/plugins/kibana_legacy", diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 5564b4cdcf79da..38b053ea127521 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -93,6 +93,10 @@ for use in their own application. |Moves the legacy ui/registry/feature_catalogue module for registering "features" that should be shown in the home page's feature catalogue to a service within a "home" plugin. The feature catalogue refered to here should not be confused with the "feature" plugin for registering features used to derive UI capabilities for feature controls. +|{kib-repo}blob/{branch}/src/plugins/index_pattern_field_editor/README.md[indexPatternFieldEditor] +|The reusable field editor across Kibana! + + |{kib-repo}blob/{branch}/src/plugins/index_pattern_management[indexPatternManagement] |WARNING: Missing README. diff --git a/packages/kbn-monaco/src/painless/diagnostics_adapter.ts b/packages/kbn-monaco/src/painless/diagnostics_adapter.ts index fd08cc9c6b57a2..dc5f1ed95205cf 100644 --- a/packages/kbn-monaco/src/painless/diagnostics_adapter.ts +++ b/packages/kbn-monaco/src/painless/diagnostics_adapter.ts @@ -18,7 +18,12 @@ const toDiagnostics = (error: PainlessError): monaco.editor.IMarkerData => { }; }; +export interface SyntaxErrors { + [modelId: string]: PainlessError[]; +} export class DiagnosticsAdapter { + private errors: SyntaxErrors = {}; + constructor(private worker: WorkerAccessor) { const onModelAdd = (model: monaco.editor.IModel): void => { let handle: any; @@ -55,8 +60,16 @@ export class DiagnosticsAdapter { if (errorMarkers) { const model = monaco.editor.getModel(resource); + this.errors = { + ...this.errors, + [model!.id]: errorMarkers, + }; // Set the error markers and underline them with "Error" severity monaco.editor.setModelMarkers(model!, ID, errorMarkers.map(toDiagnostics)); } } + + public getSyntaxErrors() { + return this.errors; + } } diff --git a/packages/kbn-monaco/src/painless/index.ts b/packages/kbn-monaco/src/painless/index.ts index 5845186776b486..68582097564308 100644 --- a/packages/kbn-monaco/src/painless/index.ts +++ b/packages/kbn-monaco/src/painless/index.ts @@ -8,8 +8,14 @@ import { ID } from './constants'; import { lexerRules, languageConfiguration } from './lexer_rules'; -import { getSuggestionProvider } from './language'; +import { getSuggestionProvider, getSyntaxErrors } from './language'; -export const PainlessLang = { ID, getSuggestionProvider, lexerRules, languageConfiguration }; +export const PainlessLang = { + ID, + getSuggestionProvider, + lexerRules, + languageConfiguration, + getSyntaxErrors, +}; export { PainlessContext, PainlessAutocompleteField } from './types'; diff --git a/packages/kbn-monaco/src/painless/language.ts b/packages/kbn-monaco/src/painless/language.ts index 74199561bc3948..3cb26d970fc7d0 100644 --- a/packages/kbn-monaco/src/painless/language.ts +++ b/packages/kbn-monaco/src/painless/language.ts @@ -13,7 +13,7 @@ import { ID } from './constants'; import { PainlessContext, PainlessAutocompleteField } from './types'; import { PainlessWorker } from './worker'; import { PainlessCompletionAdapter } from './completion_adapter'; -import { DiagnosticsAdapter } from './diagnostics_adapter'; +import { DiagnosticsAdapter, SyntaxErrors } from './diagnostics_adapter'; const workerProxyService = new WorkerProxyService(); const editorStateService = new EditorStateService(); @@ -33,8 +33,15 @@ export const getSuggestionProvider = ( return new PainlessCompletionAdapter(worker, editorStateService); }; +let diagnosticsAdapter: DiagnosticsAdapter; + +// Returns syntax errors for all models by model id +export const getSyntaxErrors = (): SyntaxErrors => { + return diagnosticsAdapter.getSyntaxErrors(); +}; + monaco.languages.onLanguage(ID, async () => { workerProxyService.setup(); - new DiagnosticsAdapter(worker); + diagnosticsAdapter = new DiagnosticsAdapter(worker); }); diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 6d81b39df71135..1a157624d7a8ab 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -33,7 +33,7 @@ pageLoadAssetSize: home: 41661 indexLifecycleManagement: 107090 indexManagement: 140608 - indexPatternManagement: 154222 + indexPatternManagement: 28222 infra: 204800 fleet: 415829 ingestPipelines: 58003 @@ -103,6 +103,7 @@ pageLoadAssetSize: stackAlerts: 29684 presentationUtil: 28545 spacesOss: 18817 + indexPatternFieldEditor: 90489 osquery: 107090 fileUpload: 25664 banners: 17946 diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 937a89e12b7553..77792286d6839f 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -121,6 +121,7 @@ export class DocLinksService { indexPatterns: { loadingData: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/tutorial-load-dataset.html`, introduction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index-patterns.html`, + fieldFormattersString: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/field-formatters-string.html`, }, addData: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/connect-to-elasticsearch.html`, kibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index.html`, diff --git a/src/plugins/data/common/index_patterns/constants.ts b/src/plugins/data/common/index_patterns/constants.ts new file mode 100644 index 00000000000000..88309447a8a29c --- /dev/null +++ b/src/plugins/data/common/index_patterns/constants.ts @@ -0,0 +1,9 @@ +/* + * 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 RUNTIME_FIELD_TYPES = ['keyword', 'long', 'double', 'date', 'ip', 'boolean'] as const; diff --git a/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap b/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap index 4ef61ec0f25571..6b1d01e5ba1429 100644 --- a/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap +++ b/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap @@ -14,7 +14,7 @@ Object { }, "count": 1, "esTypes": Array [ - "text", + "keyword", ], "lang": "lang", "name": "name", @@ -49,7 +49,7 @@ Object { "count": 1, "customLabel": undefined, "esTypes": Array [ - "text", + "keyword", ], "format": Object { "id": "number", diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts index 85e20c5a32662e..48342a9e02a2bd 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts @@ -26,7 +26,7 @@ describe('Field', function () { script: 'script', lang: 'lang', count: 1, - esTypes: ['text'], + esTypes: ['text'], // note, this will get replaced by the runtime field type aggregatable: true, filterable: true, searchable: true, @@ -71,7 +71,7 @@ describe('Field', function () { }); it('sets type field when _source field', () => { - const field = getField({ name: '_source' }); + const field = getField({ name: '_source', runtimeField: undefined }); expect(field.type).toEqual('_source'); }); diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts index 4a6ee1149d4c6d..e5f4945c9ad6d4 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts @@ -7,7 +7,7 @@ */ import type { RuntimeField } from '../types'; -import { KbnFieldType, getKbnFieldType } from '../../kbn_field_types'; +import { KbnFieldType, getKbnFieldType, castEsToKbnFieldTypeName } from '../../kbn_field_types'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import type { IFieldType } from './types'; import { FieldSpec, IndexPattern } from '../..'; @@ -99,11 +99,13 @@ export class IndexPatternField implements IFieldType { } public get type() { - return this.spec.type; + return this.runtimeField?.type + ? castEsToKbnFieldTypeName(this.runtimeField?.type) + : this.spec.type; } public get esTypes() { - return this.spec.esTypes; + return this.runtimeField?.type ? [this.runtimeField?.type] : this.spec.esTypes; } public get scripted() { diff --git a/src/plugins/data/common/index_patterns/index.ts b/src/plugins/data/common/index_patterns/index.ts index 1cea49bcbecd30..7f6249caceb52e 100644 --- a/src/plugins/data/common/index_patterns/index.ts +++ b/src/plugins/data/common/index_patterns/index.ts @@ -12,3 +12,4 @@ export { IndexPatternsService, IndexPatternsContract } from './index_patterns'; export type { IndexPattern } from './index_patterns'; export * from './errors'; export * from './expressions'; +export * from './constants'; diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap index 4aadddfad3b970..7757e2fdd4584d 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap @@ -565,7 +565,9 @@ Object { "conflictDescriptions": undefined, "count": 0, "customLabel": undefined, - "esTypes": undefined, + "esTypes": Array [ + "keyword", + ], "format": Object { "id": "number", "params": Object { @@ -587,7 +589,7 @@ Object { "searchable": false, "shortDotsEnable": false, "subType": undefined, - "type": undefined, + "type": "string", }, "script date": Object { "aggregatable": true, diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index ca4fee0416ac41..41ce7ba4bab4a1 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -389,6 +389,8 @@ export class IndexPattern implements IIndexPattern { existingField.runtimeField = undefined; } else { // runtimeField only + this.setFieldCustomLabel(name, null); + this.deleteFieldFormat(name); this.fields.remove(existingField); } } @@ -423,7 +425,6 @@ export class IndexPattern implements IIndexPattern { if (fieldObject) { fieldObject.customLabel = newCustomLabel; - return; } this.setFieldAttrs(fieldName, 'customLabel', newCustomLabel); diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index 57a452dcb12046..27794094236046 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -415,11 +415,10 @@ export class IndexPatternsService { }, spec.fieldAttrs ); - // APPLY RUNTIME FIELDS + // CREATE RUNTIME FIELDS for (const [key, value] of Object.entries(runtimeFieldMap || {})) { - if (spec.fields[key]) { - spec.fields[key].runtimeField = value; - } else { + // do not create runtime field if mapped field exists + if (!spec.fields[key]) { spec.fields[key] = { name: key, type: castEsToKbnFieldTypeName(value.type), diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 6d7327e7fb38d7..c906b809b08c4c 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -10,15 +10,16 @@ import { ToastInputFields, ErrorToastOptions } from 'src/core/public/notificatio // eslint-disable-next-line import type { SavedObject } from 'src/core/server'; import { IFieldType } from './fields'; +import { RUNTIME_FIELD_TYPES } from './constants'; import { SerializedFieldFormat } from '../../../expressions/common'; import { KBN_FIELD_TYPES, IndexPatternField, FieldFormat } from '..'; export type FieldFormatMap = Record; -const RUNTIME_FIELD_TYPES = ['keyword', 'long', 'double', 'date', 'ip', 'boolean'] as const; -type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; + +export type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; export interface RuntimeField { type: RuntimeType; - script: { + script?: { source: string; }; } diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts index 40f44f3671f312..181bd9959c1bbd 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts @@ -211,11 +211,12 @@ export function useForm( // ---------------------------------- const addField: FormHook['__addField'] = useCallback( (field) => { + const fieldExists = fieldsRefs.current[field.path] !== undefined; fieldsRefs.current[field.path] = field; updateFormDataAt(field.path, field.value); - if (!field.isValidated) { + if (!fieldExists && !field.isValidated) { setIsValid(undefined); // When we submit the form (and set "isSubmitted" to "true"), we validate **all fields**. diff --git a/src/plugins/index_pattern_field_editor/README.md b/src/plugins/index_pattern_field_editor/README.md new file mode 100644 index 00000000000000..10949954cef38f --- /dev/null +++ b/src/plugins/index_pattern_field_editor/README.md @@ -0,0 +1,69 @@ +# Index pattern field editor + +The reusable field editor across Kibana! + +This editor can be used to + +* create or edit a runtime field inside an index pattern. +* edit concrete (mapped) fields. In this case certain functionalities will be disabled like the possibility to change the field _type_ or to set the field _value_. + +## How to use + +You first need to add in your kibana.json the "`indexPatternFieldEditor`" plugin as a required dependency of your plugin. + +You will then receive in the start contract of the indexPatternFieldEditor plugin the following API: + +### `openEditor(options: OpenFieldEditorOptions): CloseEditor` + +Use this method to open the index pattern field editor to either create (runtime) or edit (concrete | runtime) a field. + +#### `options` + +`ctx: FieldEditorContext` (**required**) + +This is the only required option. You need to provide the context in which the editor is being consumed. This object has the following properties: + +- `indexPattern: IndexPattern`: the index pattern you want to create/edit the field into. + +`onSave(field: IndexPatternField): void` (optional) + +You can provide an optional `onSave` handler to be notified when the field has being created/updated. This handler is called after the field has been persisted to the saved object. + +`fieldName: string` (optional) + +You can optionally pass the name of a field to edit. Leave empty to create a new runtime field based field. + +### `userPermissions.editIndexPattern(): boolean` + +Convenience method that uses the `core.application.capabilities` api to determine whether the user can edit the index pattern. + +### `` + +This children func React component provides a handler to delete one or multiple runtime fields. + +#### Props + +* `indexPattern: IndexPattern`: the current index pattern. (**required**) + +```js + +const { DeleteRuntimeFieldProvider } = indexPatternFieldEditor; + +// Single field + + {(deleteField) => ( + deleteField('myField')}> + Delete + + )} + + +// Multiple fields + + {(deleteFields) => ( + deleteFields(['field1', 'field2', 'field3'])}> + Delete + + )} + +``` diff --git a/src/plugins/index_pattern_field_editor/jest.config.js b/src/plugins/index_pattern_field_editor/jest.config.js new file mode 100644 index 00000000000000..fc358c37116c98 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/index_pattern_field_editor'], +}; diff --git a/src/plugins/index_pattern_field_editor/kibana.json b/src/plugins/index_pattern_field_editor/kibana.json new file mode 100644 index 00000000000000..1e44b43ab36390 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "indexPatternFieldEditor", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["data"], + "optionalPlugins": ["usageCollection"], + "requiredBundles": ["kibanaReact", "esUiShared", "usageCollection"] +} diff --git a/src/plugins/index_pattern_field_editor/public/assets/icons/LICENSE.txt b/src/plugins/index_pattern_field_editor/public/assets/icons/LICENSE.txt new file mode 100644 index 00000000000000..1a86627c4a6b8c --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/assets/icons/LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Steven Skelton + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/plugins/index_pattern_field_editor/public/assets/icons/cv.png b/src/plugins/index_pattern_field_editor/public/assets/icons/cv.png new file mode 100644 index 0000000000000000000000000000000000000000..8f2ff8432e6bd2a6fb6e52b5d11f47b679e69d1f GIT binary patch literal 802 zcmV+-1Ks?IP)fIA97IawaZjI6Pk1u^EYXI}IFh z1Qc-v6>axykuMMPB-A8P{?aRL)^0~2u;A!$)fPd7SS1Qc?Ahqru$wS0xOe1x=s zf|-GWlY4`-d4slkgSLBwwSI%4f`N>DgtGtt|Ns5&|K(@@;#mLu<^TTo|LTJO-bMfU z)%xS%`r_dF;oJJoc>2t4`ry|3(v14aVfx<6`r_f@Q9|KMJmE_{;Y&N=N;%<6I^j$_ z;!Qc>OFrUHIOJ6_`rz04(~bMhZTj8F`_6Ft$UXYnw*UV6|K)c7+eH8R;{WWM|KVo; z`{rnIsbg)XWod?FW`AUCpJQ#OVr-{kZKq;wrebQGV{4paY@rJqbpsS|8YO2+NKz6X zY6KK;2p4ZSJ6be2TnHC%4azP!z`T@1G_`)JcjjAP$bcfUC3m7AhzR z1)+!#Cl@>DQ;35PBe*)cm?;|Dn&Z$WIcXF70G{c)@Z+Atxfg|6TKq4BY$*zm<$t3P zfJyXat!Rz1l1Urd1VG6K@K!@?RdI>{NPRI;%+*owx_YAt^h{W(&_Uw zi|gqT@T~k0cpUjP!o*L2{lhjv*T7lM^K7Gz1DbceH9Ks!4CiC>R0&VhbP8O#e}FF%Mxo5AVP*p5HLn1PDccY zShg**6JY?rJU>NJDwfKRr%O>4QEZJN8dMq_4FHUAgGMY>A{Zz^(iLhZVYI%H04iim z!UhT-;%nGQhJt6*B0^(AlGLb_(qx42Fpy!O;{+-M6N3iThiV<&z$DD_(($|1O(KA^ zA()a$_`Rr9ej><5wFpQdQUj$BIT#F~5y=#42rVQ4gmIe$g_2+>5Tem3L39WPUp@pp zn^q>L3pi0PbKw?~kbz+uI*F8(l|{@7BBI)K5}8J$ksz1^!-052pl+KQ6B`26I`26K z4x*E46&g%|szEEGSc2*?CIPSX_ZC!|SF~!~%Q4{tBN@aR5}62DTbc{x^Zzu z!vx45^}d!^m$XfTkOYVh)oZ2r;^f}eR2n*4i-<8)n}nht&P_2f1I18X2C4zs?AfV- zetfZ1p|(2L&Z78ydW>3!iPcghhQlP_JVb>;MyEvv(;`C2FqgxPB$K(pp=@qMB+TZ( z)F5^cCyF|U<)Bi%3Q=QoSlJ)gpf_Tzf>3Gj%p63k*ow%aw5ST4ZJe%nO)ViX6=GAP zqTa~&5-WR6Ewk7V2zn!ygcn1yuJ=#to^#KqD%Qto=G zXH$U3z24s5+E%ctwsva#=~DwLd{B4Y?JY&s!j|Hm^I>_xM=2-82L%q7>PNoraX)bW z>L=%aaKAZN#_Y%(>hJF_HNs{8%hBs6SKG3>GK)i;8cc4&9>{n8@;*+H+R+60ZrG*T z=qGdDZ|~##?ShM8ZxfE$Ev;mA>|=)bxKS_Pu=O8R7^Z)u$vqe!OedO9Abkg?a*E+n z6eSh7?7FRN|A>4f)@xwvdTrVVQ`tkds}xVf;jE*gl?4v*RFxRId*T5Gk(WyRNS|HE zy|WxLJ&R#~c`w$d!lym#ORsNlLgf3BK_M8@9)#Mu9dl(lhd!s+_Ni+Zer4X`sd)dW z@`6a1xyNJ8aeZK^>pEz(Cg6gJaQZI}j{Vdz% ziIVS*0wsrKYvgMm!wde?;Jn}bc`1vv_kp(ffTQPDn^dO#CZy<4Ndr)Ry@yinLQ%6aGK4#vhE9<9jPRc+M7(}_0!mP3bvua*x5Z5o%{ zd;guJla`OAOkULzPf@_#ywAEX=6kev#y`?`bw7PE?)^nB%Ff!^A9k}bja4pmLSoKHMd2DPGjFhSLw|IvZ~)^;^zBs2khc3GZu?wnf(u+ zG$74i6(=m&2_L_lRJ3zZs1b%|#*Qs?ukjH6_|cBx6W1IgK0jFfcVCCc|78@MZ+x=K zG%a?$m*uZ{w8o(6Z5_{O9lX;myK62fOdXy&ckbNyd(^|0WO@nrt(2r2yIr%-mjC0F zhtLl>42uiCUz~b^_EUV+q63cbM&66BZO_7uH8nLo*~*lZlo2NRsn=q@cSMJ*#E-(Rj)4$`PUUzjN?E1RB}n25WvWpdf%=w=%`K)T3&UVGpT Qv-K|-!%g5+MvC(O52$Psod5s; literal 0 HcmV?d00001 diff --git a/src/plugins/index_pattern_field_editor/public/assets/icons/ne.png b/src/plugins/index_pattern_field_editor/public/assets/icons/ne.png new file mode 100644 index 0000000000000000000000000000000000000000..d331209e179988c1a885b3048c7cbd39d3a297ca GIT binary patch literal 336 zcmV-W0k8gvP)-Ns z|K-yEyN3U>cmLC}|N8gm^x)_905+ij0001-o+w)Y004MNL_t(2 z&+X5-5`sVwMbSGV%HU4BZ@4Sq^8bHlh6Ynz9b2CG2JgY2GoWEWRBOhqc9KK^_*&ZO z_WA=K@D&Y5naFseM$?%9^MxA4O9!x0qxB{Ox6SoUyS>QanCg)~oiEp0@LlmyR%LZB i0(cnkG~i|M+tC{bDNJMSx|^i{0000A7{?nJZW+VqtF_Z)C?=@51lbD=CT;_Y1C-RZwA+MG+F^!)O(?{w=)^Iu zck9p==C(%O=$5UNIh`_rj!_t_D{LT)vNBre4~||-+q+)xN};_w?@#bazR&lQCwX3c zlP@T!_%LJlzTF%SC!_Rev5@sQSiQ0r`+`&?cGS@R)FwmT~x@3L#hs!C~Mw@gG&t2&|-My&L;c{I}p~b)GneVu2X#CMytBr~PLIjXP zfD{AxF|}W+_KN`ah)V%~4fyN?xZc$DSM$Yw2@sG&bH@RXu#*sWM(Zw2D?qCZcq|2w z8qiS#J{Na}>p+XL%UA>2gq?V!c1Cq+w9Po6x_SR|_Z_8sfMFOaxruLBWB4kI!TIxl zw-?^+C`9c&3`3LH5{W*e>13RM!>}oth!N=1?=S1c9k+mAdgD=NVhcytM-p2Yi7khH z6Eu}zsOWFufxQ82zh^yj1?C0N+t?68Z$|tUGU6oB1q>OdY1)fhi~J)80{3$K69;_e ztk6_Ra5_Nz8woCuxH}OKZ^e8}!ryz_Aka6RHDrEc$Za!Zf7ka`l^!zuV`L}{3p|OF z2u*DzVk-=jTv{BGw9b|^O_wPr%a!Kx=KJNXBW2AGs=t2Zc8)PjVw+fwlfG@z;dTxV zjn52^&kc{wnZ};LE>kkTwoQ5%nic^OnR>Rh>-w3C16As!|LlUb9vZ1t&q&n)F@Q>dkQj)t70O9BK4$Ih7GZ^mjrl(Z?D?aG{enx2 zkBHx#RV>Ie_i%Tfcsc9A*%XL7=E~O>i1|T{0HgLoujlMhX1da$^q=gfH6|{vn)l_o z%5RHwu9AF1s)^L*GONh(uY1hI#Q?sG-Mb} zwL7xB2kUS4ZmK4Ds7!Qi>XAy6+K2pFknR1d0n%`1_2weirT4r?8lYFXpO`=Bf6>>H zYJE4C23~mB-0z;qknBidu5x*+lx3d>*9aN(`lu5rBrR2uzIk&?EMAXBon!Q mgP{)J@ExT0SMl1b_?$!c@-pl{qpNISPAR{l`1eBD5B~v;Q#?8V literal 0 HcmV?d00001 diff --git a/src/plugins/index_pattern_field_editor/public/assets/icons/stop.png b/src/plugins/index_pattern_field_editor/public/assets/icons/stop.png new file mode 100644 index 0000000000000000000000000000000000000000..4bf65fc96f59fd0ff9ac094c903510dae4c30ae9 GIT binary patch literal 1912 zcmaJ?X;2eq7!GJT6qUmRu$DE52LrpCaD)V+h9qc|K#URbKn%%3A|xBK8;PiBK|#d> z5fBkP0To5drPZLwKpl~KD=JVy5C^J8KphYTxWBLj({k1Ivs+k5S8jmc(`I4l(<;ys>B?J88`?g zQOi}h9904aMsXOb!I@-2>DLq#s_(Q)>|2?Lf1P(Ote9TKtcpVHEIdbxNt{9C>4XPM#MO(7NKa=a1{kI6h|=`ssh;Tp{fD1`C^G& zX*irSgyQoVJSB#Ul@f%&1Lyg*&M2;8{3WJ z=Q)h!pc0J&QR2f`=?^SxM66*T6e=Pz2T{w{BT_##ssM%(XUIp^LK~6q8&*297OxSp z5FrLKwD(`_9zG&w$gurByoB+6{1GKF^J-$a$M;CK5_jemkHZpa>&urdj-0V<;y{7& z>J?o<(?y%xDTkz)wziJs>hu+VY}Yq-ERr($9-<|Vb@=x}$& zGnp}%dhFHo#~pf{d7-JWz~499B?8>ES$Bt-{XPfN)0ZC*`WEKoeQ(lH=SH~a^#nP<&nh?SI?Z31o!g)yjgWLgxnKU;jC`do1 z3ceN^60&)X8^1B?VrM~XbHDqIzkW}OIbb`jp)v!us*c{jdR$v^LgFmAei22(tn4kD z$e&=ljS(-H3nn1XI_;g(>gP+SzL<03XR>RqeY|o_U*`HiLh%lFenovl)07K3Fkd(K zx&C9P*s>+hEOX&j+DY3>Wwt$qLcjc1beYb5QKLQYeu42MQyJzUU-)y6#v(CwhKu$I zp4(>Bc$nh+#K~gHZ?Cs{FEW|4)8VBwvFiA#m?U;|^1+B3Jzc)3Ycote$aCgzE@ulJ z%>7z|+qFhfZk5 zzr(7wTc=>Bf8NoXE~w_P=0n+Ea_`t2&Z8W+KiwcvBo&P6PR_kw)Ewu3`Q6w@IkDk0 zgt2jPw25|n?^#b9n6-ONPKIT5RC(1ke4OdmFZtr~{!MvMqn$>)1<)^{Pvdxbbv3VK zTJwOL#Wh)7<0H0B+SxRa+d7+thO#xeCR^H14#xblhZ|DahdDR5Td88xY|egaY;N@9}v;NtAmV88RhB_$=g=7w(8$@^YM&t<2j z3wc+z&8$vuUGY#OUy+DMB|KbKp)OvRwrb|7vZ|VO_Ja>!;|w*1y)Sul1B9S0XGY)c zD?F5$DQNBYR$JCTd#x)fqEwWYe(0*Jt9uJu+&=ZGFQc|i8y+6MF)nUkRqCgeI|psn YlFp|X8_`>;bcTN+j~l=_@4GhsA7XF;BLDyZ literal 0 HcmV?d00001 diff --git a/src/plugins/index_pattern_field_editor/public/assets/icons/us.png b/src/plugins/index_pattern_field_editor/public/assets/icons/us.png new file mode 100644 index 0000000000000000000000000000000000000000..f30f21f85d06a0f4fee61bad2f6ad4bdd26ffb4c GIT binary patch literal 1074 zcmV-21kL-2P)Fc7vXIhM|a)tN;K1JWh2Y*VMGGoxgeO4kpUSn z8iHkCO28F9lE+l!29^8F2d3VNFn;64P$Z+oC3K%(7;L~T^?MA;jEB@E=Av6?%y`}i zWZz*v1CUXd9{Dm(LN`gt|GL)EUrvncodiH?R)sTe^JeToQ>1OR-;P@l?4G|Yj4!@O z@Sd=2KsTvSobkRA<3#D-zrJNLPV!US%Wb*OxrzbJItH~@@nD1QY%XGC{IejnslEW+ zq&bWW)kRbvC^EKa2|K))T{p*C3p1Pw5LY}1PtHx&QyGU zej-Tsi>WQ2d&=is^YvmxbCa^I%C0I##-%a6;ZOd&+dhM_+vxKW43p-h%|33$m{IuM z5ai8unirxl-6X>JpMmNBe+D4M$N-_37#V+|D%#FK7%&PDqi8uXig<}p#6^rEPGS_X s5u=EO7)6Z4D3T&Zku))i42Vz!08vwAal)*8c>n+a07*qoM6N<$f>$NdB>(^b literal 0 HcmV?d00001 diff --git a/src/plugins/index_pattern_field_editor/public/components/delete_field_provider/delete_field_provider.tsx b/src/plugins/index_pattern_field_editor/public/components/delete_field_provider/delete_field_provider.tsx new file mode 100644 index 00000000000000..a42e1c18c1a614 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/delete_field_provider/delete_field_provider.tsx @@ -0,0 +1,129 @@ +/* + * 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 React, { useState, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; + +type DeleteFieldFunc = (fieldName: string | string[]) => void; + +export interface Props { + children: (deleteFieldHandler: DeleteFieldFunc) => React.ReactNode; + onConfirmDelete: (fieldsToDelete: string[]) => Promise; +} + +interface State { + isModalOpen: boolean; + fieldsToDelete: string[]; +} + +const geti18nTexts = (fieldsToDelete?: string[]) => { + let modalTitle = ''; + if (fieldsToDelete) { + const isSingle = fieldsToDelete.length === 1; + + modalTitle = isSingle + ? i18n.translate( + 'indexPatternFieldEditor.deleteRuntimeField.confirmModal.deleteSingleTitle', + { + defaultMessage: `Remove field '{name}'?`, + values: { name: fieldsToDelete[0] }, + } + ) + : i18n.translate( + 'indexPatternFieldEditor.deleteRuntimeField.confirmModal.deleteMultipleTitle', + { + defaultMessage: `Remove {count} fields?`, + values: { count: fieldsToDelete.length }, + } + ); + } + + return { + modalTitle, + confirmButtonText: i18n.translate( + 'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.removeButtonLabel', + { + defaultMessage: 'Remove', + } + ), + cancelButtonText: i18n.translate( + 'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + ), + warningMultipleFields: i18n.translate( + 'indexPatternFieldEditor.deleteRuntimeField.confirmModal.multipleDeletionDescription', + { + defaultMessage: 'You are about to remove these runtime fields:', + } + ), + }; +}; + +export const DeleteRuntimeFieldProvider = ({ children, onConfirmDelete }: Props) => { + const [state, setState] = useState({ isModalOpen: false, fieldsToDelete: [] }); + + const { isModalOpen, fieldsToDelete } = state; + const i18nTexts = geti18nTexts(fieldsToDelete); + const { modalTitle, confirmButtonText, cancelButtonText, warningMultipleFields } = i18nTexts; + const isMultiple = Boolean(fieldsToDelete.length > 1); + + const deleteField: DeleteFieldFunc = useCallback((fieldNames) => { + setState({ + isModalOpen: true, + fieldsToDelete: Array.isArray(fieldNames) ? fieldNames : [fieldNames], + }); + }, []); + + const closeModal = useCallback(() => { + setState({ isModalOpen: false, fieldsToDelete: [] }); + }, []); + + const confirmDelete = useCallback(async () => { + try { + await onConfirmDelete(fieldsToDelete); + closeModal(); + } catch (e) { + // silently fail as "onConfirmDelete" is responsible + // to show a toast message if there is an error + } + }, [closeModal, onConfirmDelete, fieldsToDelete]); + + return ( + <> + {children(deleteField)} + + {isModalOpen && ( + + + {isMultiple && ( + <> +

{warningMultipleFields}

+
    + {fieldsToDelete.map((fieldName) => ( +
  • {fieldName}
  • + ))} +
+ + )} +
+
+ )} + + ); +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/delete_field_provider/get_delete_provider.tsx b/src/plugins/index_pattern_field_editor/public/components/delete_field_provider/get_delete_provider.tsx new file mode 100644 index 00000000000000..c8f1ad90357617 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/delete_field_provider/get_delete_provider.tsx @@ -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 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 React, { useCallback } from 'react'; + +import { i18n } from '@kbn/i18n'; +import { NotificationsStart } from 'src/core/public'; +import { IndexPattern, UsageCollectionStart } from '../../shared_imports'; +import { pluginName } from '../../constants'; +import { DeleteRuntimeFieldProvider, Props as DeleteProviderProps } from './delete_field_provider'; +import { DataPublicPluginStart } from '../../../../data/public'; + +export interface Props extends Omit { + indexPattern: IndexPattern; + onDelete?: (fieldNames: string[]) => void; +} + +export const getDeleteProvider = ( + indexPatternService: DataPublicPluginStart['indexPatterns'], + usageCollection: UsageCollectionStart, + notifications: NotificationsStart +): React.FunctionComponent => { + return React.memo(({ indexPattern, children, onDelete }: Props) => { + const deleteFields = useCallback( + async (fieldNames: string[]) => { + fieldNames.forEach((fieldName) => { + indexPattern.removeRuntimeField(fieldName); + }); + + try { + usageCollection.reportUiCounter( + pluginName, + usageCollection.METRIC_TYPE.COUNT, + 'delete_runtime' + ); + // eslint-disable-next-line no-empty + } catch {} + + try { + await indexPatternService.updateSavedObject(indexPattern); + } catch (e) { + const title = i18n.translate('indexPatternFieldEditor.save.deleteErrorTitle', { + defaultMessage: 'Failed to save field removal', + }); + notifications.toasts.addError(e, { title }); + } + + if (onDelete) { + onDelete(fieldNames); + } + }, + [onDelete, indexPattern] + ); + + return ; + }); +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/delete_field_provider/index.ts b/src/plugins/index_pattern_field_editor/public/components/delete_field_provider/index.ts new file mode 100644 index 00000000000000..b93b7b92560ecf --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/delete_field_provider/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { getDeleteProvider, Props as DeleteProviderProps } from './get_delete_provider'; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/advanced_parameters_section.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/advanced_parameters_section.tsx new file mode 100644 index 00000000000000..26504eee28ddbd --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/advanced_parameters_section.tsx @@ -0,0 +1,44 @@ +/* + * 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 React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; + +interface Props { + children: React.ReactNode; +} + +export const AdvancedParametersSection = ({ children }: Props) => { + const [isVisible, setIsVisible] = useState(false); + + const toggleIsVisible = () => { + setIsVisible(!isVisible); + }; + + return ( + <> + + {isVisible + ? i18n.translate('indexPatternFieldEditor.editor.form.advancedSettings.hideButtonLabel', { + defaultMessage: 'Hide advanced settings', + }) + : i18n.translate('indexPatternFieldEditor.editor.form.advancedSettings.showButtonLabel', { + defaultMessage: 'Show advanced settings', + })} + + +
+ + {/* We ned to wrap the children inside a "div" to have our css :first-child rule */} +
{children}
+
+ + ); +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/constants.ts b/src/plugins/index_pattern_field_editor/public/components/field_editor/constants.ts new file mode 100644 index 00000000000000..82711f707fa199 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/constants.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. + */ + +import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import { RuntimeType } from '../../shared_imports'; + +export const RUNTIME_FIELD_OPTIONS: Array> = [ + { + label: 'Keyword', + value: 'keyword', + }, + { + label: 'Long', + value: 'long', + }, + { + label: 'Double', + value: 'double', + }, + { + label: 'Date', + value: 'date', + }, + { + label: 'IP', + value: 'ip', + }, + { + label: 'Boolean', + value: 'boolean', + }, +]; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.test.tsx new file mode 100644 index 00000000000000..562f15301590bb --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.test.tsx @@ -0,0 +1,212 @@ +/* + * 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 { act } from 'react-dom/test-utils'; + +import '../../test_utils/setup_environment'; +import { registerTestBed, TestBed, getCommonActions } from '../../test_utils'; +import { Field } from '../../types'; +import { FieldEditor, Props, FieldEditorFormState } from './field_editor'; + +const defaultProps: Props = { + onChange: jest.fn(), + links: { + runtimePainless: 'https://elastic.co', + }, + ctx: { + existingConcreteFields: [], + namesNotAllowed: [], + fieldTypeToProcess: 'runtime', + }, + indexPattern: { fields: [] } as any, + fieldFormatEditors: { + getAll: () => [], + getById: () => undefined, + }, + fieldFormats: {} as any, + uiSettings: {} as any, + syntaxError: { + error: null, + clear: () => {}, + }, +}; + +const setup = (props?: Partial) => { + const testBed = registerTestBed(FieldEditor, { + memoryRouter: { + wrapComponent: false, + }, + })({ ...defaultProps, ...props }) as TestBed; + + const actions = { + ...getCommonActions(testBed), + }; + + return { + ...testBed, + actions, + }; +}; + +describe('', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + let testBed: TestBed & { actions: ReturnType }; + let onChange: jest.Mock = jest.fn(); + + const lastOnChangeCall = (): FieldEditorFormState[] => + onChange.mock.calls[onChange.mock.calls.length - 1]; + + const getLastStateUpdate = () => lastOnChangeCall()[0]; + + const submitFormAndGetData = async (state: FieldEditorFormState) => { + let formState: + | { + data: Field; + isValid: boolean; + } + | undefined; + + let promise: ReturnType; + + await act(async () => { + // We can't await for the promise here as the validation for the + // "script" field has a setTimeout which is mocked by jest. If we await + // we don't have the chance to call jest.advanceTimersByTime and thus the + // test times out. + promise = state.submit(); + }); + + await act(async () => { + // The painless syntax validation has a timeout set to 600ms + // we give it a bit more time just to be on the safe side + jest.advanceTimersByTime(1000); + }); + + await act(async () => { + promise.then((response) => { + formState = response; + }); + }); + + return formState!; + }; + + beforeEach(() => { + onChange = jest.fn(); + }); + + test('initial state should have "set custom label", "set value" and "set format" turned off', () => { + testBed = setup(); + + ['customLabel', 'value', 'format'].forEach((row) => { + const testSubj = `${row}Row.toggle`; + const toggle = testBed.find(testSubj); + const isOn = toggle.props()['aria-checked']; + + try { + expect(isOn).toBe(false); + } catch (e) { + e.message = `"${row}" row toggle expected to be 'off' but was 'on'. \n${e.message}`; + throw e; + } + }); + }); + + test('should accept a defaultValue and onChange prop to forward the form state', async () => { + const field = { + name: 'foo', + type: 'date', + script: { source: 'emit("hello")' }, + }; + + testBed = setup({ onChange, field }); + + expect(onChange).toHaveBeenCalled(); + + let lastState = getLastStateUpdate(); + expect(lastState.isValid).toBe(undefined); + expect(lastState.isSubmitted).toBe(false); + expect(lastState.submit).toBeDefined(); + + const { data: formData } = await submitFormAndGetData(lastState); + expect(formData).toEqual(field); + + // Make sure that both isValid and isSubmitted state are now "true" + lastState = getLastStateUpdate(); + expect(lastState.isValid).toBe(true); + expect(lastState.isSubmitted).toBe(true); + }); + + describe('validation', () => { + test('should accept an optional list of existing fields and prevent creating duplicates', async () => { + const existingFields = ['myRuntimeField']; + testBed = setup({ + onChange, + ctx: { + namesNotAllowed: existingFields, + existingConcreteFields: [], + fieldTypeToProcess: 'runtime', + }, + }); + + const { form, component, actions } = testBed; + + await act(async () => { + actions.toggleFormRow('value'); + }); + + await act(async () => { + form.setInputValue('nameField.input', existingFields[0]); + form.setInputValue('scriptField', 'echo("hello")'); + }); + + await act(async () => { + jest.advanceTimersByTime(1000); // Make sure our debounced error message is in the DOM + }); + + const lastState = getLastStateUpdate(); + await submitFormAndGetData(lastState); + component.update(); + expect(getLastStateUpdate().isValid).toBe(false); + expect(form.getErrorsMessages()).toEqual(['A field with this name already exists.']); + }); + + test('should not count the default value as a duplicate', async () => { + const existingRuntimeFieldNames = ['myRuntimeField']; + const field: Field = { + name: 'myRuntimeField', + type: 'boolean', + script: { source: 'emit("hello"' }, + }; + + testBed = setup({ + field, + onChange, + ctx: { + namesNotAllowed: existingRuntimeFieldNames, + existingConcreteFields: [], + fieldTypeToProcess: 'runtime', + }, + }); + + const { form, component } = testBed; + const lastState = getLastStateUpdate(); + await submitFormAndGetData(lastState); + component.update(); + expect(getLastStateUpdate().isValid).toBe(true); + expect(form.getErrorsMessages()).toEqual([]); + }); + }); +}); diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.tsx new file mode 100644 index 00000000000000..afb87bd1e73344 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.tsx @@ -0,0 +1,286 @@ +/* + * 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 React, { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiComboBoxOptionOption, + EuiCode, +} from '@elastic/eui'; +import type { CoreStart } from 'src/core/public'; + +import { + Form, + useForm, + FormHook, + UseField, + TextField, + RuntimeType, + IndexPattern, + DataPublicPluginStart, +} from '../../shared_imports'; +import { Field, InternalFieldType, PluginStart } from '../../types'; + +import { RUNTIME_FIELD_OPTIONS } from './constants'; +import { schema } from './form_schema'; +import { getNameFieldConfig } from './lib'; +import { + TypeField, + CustomLabelField, + ScriptField, + FormatField, + PopularityField, + ScriptSyntaxError, +} from './form_fields'; +import { FormRow } from './form_row'; +import { AdvancedParametersSection } from './advanced_parameters_section'; + +export interface FieldEditorFormState { + isValid: boolean | undefined; + isSubmitted: boolean; + submit: FormHook['submit']; +} + +export interface FieldFormInternal extends Omit { + type: Array>; + __meta__: { + isCustomLabelVisible: boolean; + isValueVisible: boolean; + isFormatVisible: boolean; + isPopularityVisible: boolean; + }; +} + +export interface Props { + /** Link URLs to our doc site */ + links: { + runtimePainless: string; + }; + /** Optional field to edit */ + field?: Field; + /** Handler to receive state changes updates */ + onChange?: (state: FieldEditorFormState) => void; + indexPattern: IndexPattern; + fieldFormatEditors: PluginStart['fieldFormatEditors']; + fieldFormats: DataPublicPluginStart['fieldFormats']; + uiSettings: CoreStart['uiSettings']; + /** Context object */ + ctx: { + /** The internal field type we are dealing with (concrete|runtime)*/ + fieldTypeToProcess: InternalFieldType; + /** + * An array of field names not allowed. + * e.g we probably don't want a user to give a name of an existing + * runtime field (for that the user should edit the existing runtime field). + */ + namesNotAllowed: string[]; + /** + * An array of existing concrete fields. If the user gives a name to the runtime + * field that matches one of the concrete fields, a callout will be displayed + * to indicate that this runtime field will shadow the concrete field. + * It is also used to provide the list of field autocomplete suggestions to the code editor. + */ + existingConcreteFields: Array<{ name: string; type: string }>; + }; + syntaxError: ScriptSyntaxError; +} + +const geti18nTexts = (): { + [key: string]: { title: string; description: JSX.Element | string }; +} => ({ + customLabel: { + title: i18n.translate('indexPatternFieldEditor.editor.form.customLabelTitle', { + defaultMessage: 'Set custom label', + }), + description: i18n.translate('indexPatternFieldEditor.editor.form.customLabelDescription', { + defaultMessage: `Create a label to display in place of the field name in Discover, Maps, and Visualize. Useful for shortening a long field name. Queries and filters use the original field name.`, + }), + }, + value: { + title: i18n.translate('indexPatternFieldEditor.editor.form.valueTitle', { + defaultMessage: 'Set value', + }), + description: ( + {'_source'}, + }} + /> + ), + }, + format: { + title: i18n.translate('indexPatternFieldEditor.editor.form.formatTitle', { + defaultMessage: 'Set format', + }), + description: i18n.translate('indexPatternFieldEditor.editor.form.formatDescription', { + defaultMessage: `Set your preferred format for displaying the value. Changing the format can affect the value and prevent highlighting in Discover.`, + }), + }, + popularity: { + title: i18n.translate('indexPatternFieldEditor.editor.form.popularityTitle', { + defaultMessage: 'Set popularity', + }), + description: i18n.translate('indexPatternFieldEditor.editor.form.popularityDescription', { + defaultMessage: `Adjust the popularity to make the field appear higher or lower in the fields list. By default, Discover orders fields from most selected to least selected.`, + }), + }, +}); + +const formDeserializer = (field: Field): FieldFormInternal => { + let fieldType: Array>; + if (!field.type) { + fieldType = [RUNTIME_FIELD_OPTIONS[0]]; + } else { + const label = RUNTIME_FIELD_OPTIONS.find(({ value }) => value === field.type)?.label; + fieldType = [{ label: label ?? field.type, value: field.type as RuntimeType }]; + } + + return { + ...field, + type: fieldType, + __meta__: { + isCustomLabelVisible: field.customLabel !== undefined, + isValueVisible: field.script !== undefined, + isFormatVisible: field.format !== undefined, + isPopularityVisible: field.popularity !== undefined, + }, + }; +}; + +const formSerializer = (field: FieldFormInternal): Field => { + const { __meta__, type, ...rest } = field; + return { + type: type[0].value!, + ...rest, + }; +}; + +const FieldEditorComponent = ({ + field, + onChange, + links, + indexPattern, + fieldFormatEditors, + fieldFormats, + uiSettings, + syntaxError, + ctx: { fieldTypeToProcess, namesNotAllowed, existingConcreteFields }, +}: Props) => { + const { form } = useForm({ + defaultValue: field, + schema, + deserializer: formDeserializer, + serializer: formSerializer, + }); + const { submit, isValid: isFormValid, isSubmitted } = form; + + const nameFieldConfig = getNameFieldConfig(namesNotAllowed, field); + const i18nTexts = geti18nTexts(); + + useEffect(() => { + if (onChange) { + onChange({ isValid: isFormValid, isSubmitted, submit }); + } + }, [onChange, isFormValid, isSubmitted, submit]); + + return ( +
+ + {/* Name */} + + + path="name" + config={nameFieldConfig} + component={TextField} + data-test-subj="nameField" + componentProps={{ + euiFieldProps: { + disabled: fieldTypeToProcess === 'concrete', + 'aria-label': i18n.translate('indexPatternFieldEditor.editor.form.nameAriaLabel', { + defaultMessage: 'Name field', + }), + }, + }} + /> + + + {/* Type */} + + + + + + + + {/* Set custom label */} + + + + + {/* Set value */} + {fieldTypeToProcess === 'runtime' && ( + + + + )} + + {/* Set custom format */} + + + + + {/* Advanced settings */} + + + + + + + ); +}; + +export const FieldEditor = React.memo(FieldEditorComponent); diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/custom_label_field.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/custom_label_field.tsx new file mode 100644 index 00000000000000..313137de463032 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/custom_label_field.tsx @@ -0,0 +1,15 @@ +/* + * 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 React from 'react'; + +import { UseField, TextField } from '../../../shared_imports'; + +export const CustomLabelField = () => { + return ; +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/format_field.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/format_field.tsx new file mode 100644 index 00000000000000..db98e4a1591625 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/format_field.tsx @@ -0,0 +1,82 @@ +/* + * 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 React, { useState, useEffect, useRef } from 'react'; +import { EuiCallOut, EuiSpacer } from '@elastic/eui'; + +import { UseField, useFormData, ES_FIELD_TYPES, useFormContext } from '../../../shared_imports'; +import { FormatSelectEditor, FormatSelectEditorProps } from '../../field_format_editor'; +import { FieldFormInternal } from '../field_editor'; +import { FieldFormatConfig } from '../../../types'; + +export const FormatField = ({ + indexPattern, + fieldFormatEditors, + fieldFormats, + uiSettings, +}: Omit) => { + const isMounted = useRef(false); + const [{ type }] = useFormData({ watch: ['name', 'type'] }); + const { getFields, isSubmitted } = useFormContext(); + const [formatError, setFormatError] = useState(); + // convert from combobox type to values + const typeValue = type.reduce((collector, item) => { + if (item.value !== undefined) { + collector.push(item.value as ES_FIELD_TYPES); + } + return collector; + }, [] as ES_FIELD_TYPES[]); + + useEffect(() => { + if (formatError === undefined) { + getFields().format.setErrors([]); + } else { + getFields().format.setErrors([{ message: formatError }]); + } + }, [formatError, getFields]); + + useEffect(() => { + if (isMounted.current) { + getFields().format.reset(); + } + isMounted.current = true; + }, [type, getFields]); + + return ( + path="format"> + {({ setValue, errors, value }) => { + return ( + <> + {isSubmitted && errors.length > 0 && ( + <> + err.message)} + color="danger" + iconType="cross" + data-test-subj="formFormatError" + /> + + + )} + + + + ); + }} + + ); +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/index.ts new file mode 100644 index 00000000000000..e958e1362bb054 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { TypeField } from './type_field'; + +export { CustomLabelField } from './custom_label_field'; + +export { PopularityField } from './popularity_field'; + +export { ScriptField, ScriptSyntaxError } from './script_field'; + +export { FormatField } from './format_field'; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/popularity_field.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/popularity_field.tsx new file mode 100644 index 00000000000000..44f83138fe1d31 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/popularity_field.tsx @@ -0,0 +1,21 @@ +/* + * 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 React from 'react'; + +import { UseField, NumericField } from '../../../shared_imports'; + +export const PopularityField = () => { + return ( + + ); +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/script_field.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/script_field.tsx new file mode 100644 index 00000000000000..d15445f3e10ae6 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/script_field.tsx @@ -0,0 +1,227 @@ +/* + * 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 React, { useState, useEffect, useMemo, useRef } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFormRow, EuiLink, EuiCode, EuiCodeBlock, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { PainlessLang, PainlessContext } from '@kbn/monaco'; + +import { + UseField, + useFormData, + RuntimeType, + FieldConfig, + CodeEditor, +} from '../../../shared_imports'; +import { RuntimeFieldPainlessError } from '../../../lib'; +import { schema } from '../form_schema'; +import type { FieldFormInternal } from '../field_editor'; + +interface Props { + links: { runtimePainless: string }; + existingConcreteFields?: Array<{ name: string; type: string }>; + syntaxError: ScriptSyntaxError; +} + +export interface ScriptSyntaxError { + error: RuntimeFieldPainlessError | null; + clear: () => void; +} + +const mapReturnTypeToPainlessContext = (runtimeType: RuntimeType): PainlessContext => { + switch (runtimeType) { + case 'keyword': + return 'string_script_field_script_field'; + case 'long': + return 'long_script_field_script_field'; + case 'double': + return 'double_script_field_script_field'; + case 'date': + return 'date_script_field'; + case 'ip': + return 'ip_script_field_script_field'; + case 'boolean': + return 'boolean_script_field_script_field'; + default: + return 'string_script_field_script_field'; + } +}; + +export const ScriptField = React.memo(({ existingConcreteFields, links, syntaxError }: Props) => { + const editorValidationTimeout = useRef>(); + + const [painlessContext, setPainlessContext] = useState( + mapReturnTypeToPainlessContext(schema.type.defaultValue[0].value!) + ); + + const [editorId, setEditorId] = useState(); + + const suggestionProvider = PainlessLang.getSuggestionProvider( + painlessContext, + existingConcreteFields + ); + + const [{ type, script: { source } = { source: '' } }] = useFormData({ + watch: ['type', 'script.source'], + }); + + const { clear: clearSyntaxError } = syntaxError; + + const sourceFieldConfig: FieldConfig = useMemo(() => { + return { + ...schema.script.source, + validations: [ + ...schema.script.source.validations, + { + validator: () => { + if (editorValidationTimeout.current) { + clearTimeout(editorValidationTimeout.current); + } + + return new Promise((resolve) => { + // monaco waits 500ms before validating, so we also add a delay + // before checking if there are any syntax errors + editorValidationTimeout.current = setTimeout(() => { + const painlessSyntaxErrors = PainlessLang.getSyntaxErrors(); + // It is possible for there to be more than one editor in a view, + // so we need to get the syntax errors based on the editor (aka model) ID + const editorHasSyntaxErrors = editorId && painlessSyntaxErrors[editorId].length > 0; + + if (editorHasSyntaxErrors) { + return resolve({ + message: i18n.translate( + 'indexPatternFieldEditor.editor.form.scriptEditorValidationMessage', + { + defaultMessage: 'Invalid Painless syntax.', + } + ), + }); + } + + resolve(undefined); + }, 600); + }); + }, + }, + ], + }; + }, [editorId]); + + useEffect(() => { + setPainlessContext(mapReturnTypeToPainlessContext(type[0]!.value!)); + }, [type]); + + useEffect(() => { + // Whenever the source changes we clear potential syntax errors + clearSyntaxError(); + }, [source, clearSyntaxError]); + + return ( + path="script.source" config={sourceFieldConfig}> + {({ value, setValue, label, isValid, getErrorsMessages }) => { + let errorMessage: string | null = ''; + if (syntaxError.error !== null) { + errorMessage = syntaxError.error.reason ?? syntaxError.error.message; + } else { + errorMessage = getErrorsMessages(); + } + + return ( + <> + + {i18n.translate( + 'indexPatternFieldEditor.editor.form.script.learnMoreLinkText', + { + defaultMessage: 'Learn about script syntax.', + } + )} + + ), + source: {'_source'}, + }} + /> + } + fullWidth + > + setEditorId(editor.getModel()?.id)} + options={{ + fontSize: 12, + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', + automaticLayout: true, + suggest: { + snippetsPreventQuickSuggestions: false, + }, + }} + data-test-subj="scriptField" + aria-label={i18n.translate( + 'indexPatternFieldEditor.editor.form.scriptEditorAriaLabel', + { + defaultMessage: 'Script editor', + } + )} + /> + + + {/* Help the user debug the error by showing where it failed in the script */} + {syntaxError.error !== null && ( + <> + + +

+ {i18n.translate( + 'indexPatternFieldEditor.editor.form.scriptEditor.debugErrorMessage', + { + defaultMessage: 'Syntax error detail', + } + )} +

+
+ + + {syntaxError.error.scriptStack.join('\n')} + + + )} + + ); + }} +
+ ); +}); diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/type_field.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/type_field.tsx new file mode 100644 index 00000000000000..36428579a30e86 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_fields/type_field.tsx @@ -0,0 +1,65 @@ +/* + * 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 React from 'react'; +import { i18n } from '@kbn/i18n'; + +import { EuiFormRow, EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; + +import { UseField, RuntimeType } from '../../../shared_imports'; +import { RUNTIME_FIELD_OPTIONS } from '../constants'; + +interface Props { + isDisabled?: boolean; +} + +export const TypeField = ({ isDisabled = false }: Props) => { + return ( + >> path="type"> + {({ label, value, setValue }) => { + if (value === undefined) { + return null; + } + return ( + <> + + { + if (newValue.length === 0) { + // Don't allow clearing the type. One must always be selected + return; + } + setValue(newValue); + }} + isClearable={false} + isDisabled={isDisabled} + data-test-subj="typeField" + aria-label={i18n.translate( + 'indexPatternFieldEditor.editor.form.typeSelectAriaLabel', + { + defaultMessage: 'Type select', + } + )} + fullWidth + /> + + + ); + }} + + ); +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_row.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_row.tsx new file mode 100644 index 00000000000000..66f5af09c8b2f4 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_row.tsx @@ -0,0 +1,86 @@ +/* + * 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 React from 'react'; +import { get } from 'lodash'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiText, + EuiHorizontalRule, + EuiSpacer, +} from '@elastic/eui'; + +import { UseField, ToggleField, useFormData } from '../../shared_imports'; + +interface Props { + title: string; + formFieldPath: string; + children: React.ReactNode; + description?: string | JSX.Element; + withDividerRule?: boolean; + 'data-test-subj'?: string; +} + +export const FormRow = ({ + title, + description, + children, + formFieldPath, + withDividerRule = false, + 'data-test-subj': dataTestSubj, +}: Props) => { + const [formData] = useFormData({ watch: formFieldPath }); + const isContentVisible = Boolean(get(formData, formFieldPath)); + + return ( + <> + + + + + + +
+ {/* Title */} + +

{title}

+
+ + + {/* Description */} + + {description} + + + {/* Content */} + {isContentVisible && ( + <> + + {children} + + )} +
+
+
+ + {withDividerRule && } + + ); +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/form_schema.ts b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_schema.ts new file mode 100644 index 00000000000000..a722f277b8e237 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/form_schema.ts @@ -0,0 +1,119 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { fieldValidators } from '../../shared_imports'; + +import { RUNTIME_FIELD_OPTIONS } from './constants'; + +const { emptyField, numberGreaterThanField } = fieldValidators; + +export const schema = { + name: { + label: i18n.translate('indexPatternFieldEditor.editor.form.nameLabel', { + defaultMessage: 'Name', + }), + validations: [ + { + validator: emptyField( + i18n.translate( + 'indexPatternFieldEditor.editor.form.validations.nameIsRequiredErrorMessage', + { + defaultMessage: 'A name is required.', + } + ) + ), + }, + ], + }, + type: { + label: i18n.translate('indexPatternFieldEditor.editor.form.runtimeTypeLabel', { + defaultMessage: 'Type', + }), + defaultValue: [RUNTIME_FIELD_OPTIONS[0]], + }, + script: { + source: { + label: i18n.translate('indexPatternFieldEditor.editor.form.defineFieldLabel', { + defaultMessage: 'Define script', + }), + validations: [ + { + validator: emptyField( + i18n.translate( + 'indexPatternFieldEditor.editor.form.validations.scriptIsRequiredErrorMessage', + { + defaultMessage: 'A script is required to set the field value.', + } + ) + ), + }, + ], + }, + }, + customLabel: { + label: i18n.translate('indexPatternFieldEditor.editor.form.customLabelLabel', { + defaultMessage: 'Custom label', + }), + validations: [ + { + validator: emptyField( + i18n.translate( + 'indexPatternFieldEditor.editor.form.validations.customLabelIsRequiredErrorMessage', + { + defaultMessage: 'Give a label to the field.', + } + ) + ), + }, + ], + }, + popularity: { + label: i18n.translate('indexPatternFieldEditor.editor.form.popularityLabel', { + defaultMessage: 'Popularity', + }), + validations: [ + { + validator: emptyField( + i18n.translate( + 'indexPatternFieldEditor.editor.form.validations.popularityIsRequiredErrorMessage', + { + defaultMessage: 'Give a popularity to the field.', + } + ) + ), + }, + { + validator: numberGreaterThanField({ + than: 0, + allowEquality: true, + message: i18n.translate( + 'indexPatternFieldEditor.editor.form.validations.popularityGreaterThan0ErrorMessage', + { + defaultMessage: 'The popularity must be zero or greater.', + } + ), + }), + }, + ], + }, + __meta__: { + isCustomLabelVisible: { + defaultValue: false, + }, + isValueVisible: { + defaultValue: false, + }, + isFormatVisible: { + defaultValue: false, + }, + isPopularityVisible: { + defaultValue: false, + }, + }, +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_editor/index.ts new file mode 100644 index 00000000000000..db7c05fa7ff7a4 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { FieldEditor } from './field_editor'; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/lib.ts b/src/plugins/index_pattern_field_editor/public/components/field_editor/lib.ts new file mode 100644 index 00000000000000..2d324804c9e43d --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/lib.ts @@ -0,0 +1,60 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import { ValidationFunc, FieldConfig } from '../../shared_imports'; +import { Field } from '../../types'; +import { schema } from './form_schema'; +import { Props } from './field_editor'; + +const createNameNotAllowedValidator = ( + namesNotAllowed: string[] +): ValidationFunc<{}, string, string> => ({ value }) => { + if (namesNotAllowed.includes(value)) { + return { + message: i18n.translate( + 'indexPatternFieldEditor.editor.runtimeFieldsEditor.existRuntimeFieldNamesValidationErrorMessage', + { + defaultMessage: 'A field with this name already exists.', + } + ), + }; + } +}; + +/** + * Dynamically retrieve the config for the "name" field, adding + * a validator to avoid duplicated runtime fields to be created. + * + * @param namesNotAllowed Array of names not allowed for the field "name" + * @param field Initial value of the form + */ +export const getNameFieldConfig = ( + namesNotAllowed?: string[], + field?: Props['field'] +): FieldConfig => { + const nameFieldConfig = schema.name as FieldConfig; + + if (!namesNotAllowed) { + return nameFieldConfig; + } + + // Add validation to not allow duplicates + return { + ...nameFieldConfig!, + validations: [ + ...(nameFieldConfig.validations ?? []), + { + validator: createNameNotAllowedValidator( + namesNotAllowed.filter((name) => name !== field?.name) + ), + }, + ], + }; +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/shadowing_field_warning.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/shadowing_field_warning.tsx new file mode 100644 index 00000000000000..4343b13db9a5af --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/shadowing_field_warning.tsx @@ -0,0 +1,32 @@ +/* + * 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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiCallOut } from '@elastic/eui'; + +export const ShadowingFieldWarning = () => { + return ( + +
+ {i18n.translate('indexPatternFieldEditor.editor.form.fieldShadowingCalloutDescription', { + defaultMessage: + 'This field shares the name of a mapped field. Values for this field will be returned in search results.', + })} +
+
+ ); +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts new file mode 100644 index 00000000000000..e943dbdda998df --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts @@ -0,0 +1,198 @@ +/* + * 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 { act } from 'react-dom/test-utils'; + +import '../test_utils/setup_environment'; +import { registerTestBed, TestBed, noop, docLinks, getCommonActions } from '../test_utils'; + +import { FieldEditor } from './field_editor'; +import { FieldEditorFlyoutContent, Props } from './field_editor_flyout_content'; + +const defaultProps: Props = { + onSave: noop, + onCancel: noop, + docLinks, + FieldEditor, + indexPattern: { fields: [] } as any, + uiSettings: {} as any, + fieldFormats: {} as any, + fieldFormatEditors: {} as any, + fieldTypeToProcess: 'runtime', + runtimeFieldValidator: () => Promise.resolve(null), + isSavingField: false, +}; + +const setup = (props: Props = defaultProps) => { + const testBed = registerTestBed(FieldEditorFlyoutContent, { + memoryRouter: { wrapComponent: false }, + })(props) as TestBed; + + const actions = { + ...getCommonActions(testBed), + }; + + return { + ...testBed, + actions, + }; +}; + +describe('', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + test('should have the correct title', () => { + const { exists, find } = setup(); + expect(exists('flyoutTitle')).toBe(true); + expect(find('flyoutTitle').text()).toBe('Create field'); + }); + + test('should allow a field to be provided', () => { + const field = { + name: 'foo', + type: 'ip', + script: { + source: 'emit("hello world")', + }, + }; + + const { find } = setup({ ...defaultProps, field }); + + expect(find('flyoutTitle').text()).toBe(`Edit ${field.name} field`); + expect(find('nameField.input').props().value).toBe(field.name); + expect(find('typeField').props().value).toBe(field.type); + expect(find('scriptField').props().value).toBe(field.script.source); + }); + + test('should accept an "onSave" prop', async () => { + const field = { + name: 'foo', + type: 'date', + script: { source: 'test=123' }, + }; + const onSave: jest.Mock = jest.fn(); + + const { find } = setup({ ...defaultProps, onSave, field }); + + await act(async () => { + find('fieldSaveButton').simulate('click'); + }); + + await act(async () => { + // The painless syntax validation has a timeout set to 600ms + // we give it a bit more time just to be on the safe side + jest.advanceTimersByTime(1000); + }); + + expect(onSave).toHaveBeenCalled(); + const fieldReturned = onSave.mock.calls[onSave.mock.calls.length - 1][0]; + expect(fieldReturned).toEqual(field); + }); + + test('should accept an onCancel prop', () => { + const onCancel = jest.fn(); + const { find } = setup({ ...defaultProps, onCancel }); + + find('closeFlyoutButton').simulate('click'); + + expect(onCancel).toHaveBeenCalled(); + }); + + describe('validation', () => { + test('should validate the fields and prevent saving invalid form', async () => { + const onSave: jest.Mock = jest.fn(); + + const { find, exists, form, component } = setup({ ...defaultProps, onSave }); + + expect(find('fieldSaveButton').props().disabled).toBe(false); + + await act(async () => { + find('fieldSaveButton').simulate('click'); + }); + + await act(async () => { + jest.advanceTimersByTime(1000); + }); + + component.update(); + + expect(onSave).toHaveBeenCalledTimes(0); + expect(find('fieldSaveButton').props().disabled).toBe(true); + expect(form.getErrorsMessages()).toEqual(['A name is required.']); + expect(exists('formError')).toBe(true); + expect(find('formError').text()).toBe('Fix errors in form before continuing.'); + }); + + test('should forward values from the form', async () => { + const onSave: jest.Mock = jest.fn(); + + const { + find, + component, + form, + actions: { toggleFormRow }, + } = setup({ ...defaultProps, onSave }); + + act(() => { + form.setInputValue('nameField.input', 'someName'); + toggleFormRow('value'); + }); + component.update(); + + await act(async () => { + form.setInputValue('scriptField', 'echo("hello")'); + }); + + await act(async () => { + // Let's make sure that validation has finished running + jest.advanceTimersByTime(1000); + }); + + await act(async () => { + find('fieldSaveButton').simulate('click'); + }); + + expect(onSave).toHaveBeenCalled(); + + let fieldReturned = onSave.mock.calls[onSave.mock.calls.length - 1][0]; + + expect(fieldReturned).toEqual({ + name: 'someName', + type: 'keyword', // default to keyword + script: { source: 'echo("hello")' }, + }); + + // Change the type and make sure it is forwarded + act(() => { + find('typeField').simulate('change', [ + { + label: 'Other type', + value: 'other_type', + }, + ]); + }); + + await act(async () => { + find('fieldSaveButton').simulate('click'); + }); + + fieldReturned = onSave.mock.calls[onSave.mock.calls.length - 1][0]; + + expect(fieldReturned).toEqual({ + name: 'someName', + type: 'other_type', + script: { source: 'echo("hello")' }, + }); + }); + }); +}); diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx new file mode 100644 index 00000000000000..1511836da85e73 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx @@ -0,0 +1,253 @@ +/* + * 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 React, { useState, useCallback, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFlyoutHeader, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, + EuiCallOut, + EuiSpacer, +} from '@elastic/eui'; + +import { DocLinksStart, CoreStart } from 'src/core/public'; + +import { Field, InternalFieldType, PluginStart, EsRuntimeField } from '../types'; +import { getLinks, RuntimeFieldPainlessError } from '../lib'; +import type { IndexPattern, DataPublicPluginStart } from '../shared_imports'; +import type { Props as FieldEditorProps, FieldEditorFormState } from './field_editor/field_editor'; + +const geti18nTexts = (field?: Field) => { + return { + flyoutTitle: field + ? i18n.translate('indexPatternFieldEditor.editor.flyoutEditFieldTitle', { + defaultMessage: 'Edit {fieldName} field', + values: { + fieldName: field.name, + }, + }) + : i18n.translate('indexPatternFieldEditor.editor.flyoutDefaultTitle', { + defaultMessage: 'Create field', + }), + closeButtonLabel: i18n.translate('indexPatternFieldEditor.editor.flyoutCloseButtonLabel', { + defaultMessage: 'Close', + }), + saveButtonLabel: i18n.translate('indexPatternFieldEditor.editor.flyoutSaveButtonLabel', { + defaultMessage: 'Save', + }), + formErrorsCalloutTitle: i18n.translate('indexPatternFieldEditor.editor.validationErrorTitle', { + defaultMessage: 'Fix errors in form before continuing.', + }), + }; +}; + +export interface Props { + /** + * Handler for the "save" footer button + */ + onSave: (field: Field) => void; + /** + * Handler for the "cancel" footer button + */ + onCancel: () => void; + /** + * The docLinks start service from core + */ + docLinks: DocLinksStart; + /** + * The Field editor component that contains the form to create or edit a field + */ + FieldEditor: React.ComponentType | null; + /** The internal field type we are dealing with (concrete|runtime)*/ + fieldTypeToProcess: InternalFieldType; + /** Handler to validate the script */ + runtimeFieldValidator: (field: EsRuntimeField) => Promise; + /** Optional field to process */ + field?: Field; + + indexPattern: IndexPattern; + fieldFormatEditors: PluginStart['fieldFormatEditors']; + fieldFormats: DataPublicPluginStart['fieldFormats']; + uiSettings: CoreStart['uiSettings']; + isSavingField: boolean; +} + +const FieldEditorFlyoutContentComponent = ({ + field, + onSave, + onCancel, + FieldEditor, + docLinks, + indexPattern, + fieldFormatEditors, + fieldFormats, + uiSettings, + fieldTypeToProcess, + runtimeFieldValidator, + isSavingField, +}: Props) => { + const i18nTexts = geti18nTexts(field); + + const [formState, setFormState] = useState({ + isSubmitted: false, + isValid: field ? true : undefined, + submit: field + ? async () => ({ isValid: true, data: field }) + : async () => ({ isValid: false, data: {} as Field }), + }); + + const [painlessSyntaxError, setPainlessSyntaxError] = useState( + null + ); + + const [isValidating, setIsValidating] = useState(false); + + const { submit, isValid: isFormValid, isSubmitted } = formState; + const { fields } = indexPattern; + const isSaveButtonDisabled = isFormValid === false || painlessSyntaxError !== null; + + const clearSyntaxError = useCallback(() => setPainlessSyntaxError(null), []); + + const syntaxError = useMemo( + () => ({ + error: painlessSyntaxError, + clear: clearSyntaxError, + }), + [painlessSyntaxError, clearSyntaxError] + ); + + const onClickSave = useCallback(async () => { + const { isValid, data } = await submit(); + + if (isValid) { + if (data.script) { + setIsValidating(true); + + const error = await runtimeFieldValidator({ + type: data.type, + script: data.script, + }); + + setIsValidating(false); + setPainlessSyntaxError(error); + + if (error) { + return; + } + } + + onSave(data); + } + }, [onSave, submit, runtimeFieldValidator]); + + const namesNotAllowed = useMemo(() => fields.map((fld) => fld.name), [fields]); + + const existingConcreteFields = useMemo(() => { + const existing: Array<{ name: string; type: string }> = []; + + fields + .filter((fld) => { + const isFieldBeingEdited = field?.name === fld.name; + return !isFieldBeingEdited && fld.isMapped; + }) + .forEach((fld) => { + existing.push({ + name: fld.name, + type: (fld.esTypes && fld.esTypes[0]) || '', + }); + }); + + return existing; + }, [fields, field]); + + const ctx = useMemo( + () => ({ + fieldTypeToProcess, + namesNotAllowed, + existingConcreteFields, + }), + [fieldTypeToProcess, namesNotAllowed, existingConcreteFields] + ); + + return ( + <> + + +

{i18nTexts.flyoutTitle}

+
+
+ + + {FieldEditor && ( + + )} + + + + {FieldEditor && ( + <> + {isSubmitted && isSaveButtonDisabled && ( + <> + + + + )} + + + + {i18nTexts.closeButtonLabel} + + + + + + {i18nTexts.saveButtonLabel} + + + + + )} + + + ); +}; + +export const FieldEditorFlyoutContent = React.memo(FieldEditorFlyoutContentComponent); diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content_container.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content_container.tsx new file mode 100644 index 00000000000000..ade25424c22509 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content_container.tsx @@ -0,0 +1,205 @@ +/* + * 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 React, { useCallback, useEffect, useState, useMemo } from 'react'; +import { DocLinksStart, NotificationsStart, CoreStart } from 'src/core/public'; +import { i18n } from '@kbn/i18n'; + +import { + IndexPatternField, + IndexPattern, + DataPublicPluginStart, + RuntimeType, + UsageCollectionStart, +} from '../shared_imports'; +import { Field, PluginStart, InternalFieldType } from '../types'; +import { pluginName } from '../constants'; +import { deserializeField, getRuntimeFieldValidator } from '../lib'; +import { Props as FieldEditorProps } from './field_editor/field_editor'; +import { FieldEditorFlyoutContent } from './field_editor_flyout_content'; + +export interface FieldEditorContext { + indexPattern: IndexPattern; + /** + * The Kibana field type of the field to create or edit + * Default: "runtime" + */ + fieldTypeToProcess: InternalFieldType; + /** The search service from the data plugin */ + search: DataPublicPluginStart['search']; +} + +export interface Props { + /** + * Handler for the "save" footer button + */ + onSave: (field: IndexPatternField) => void; + /** + * Handler for the "cancel" footer button + */ + onCancel: () => void; + /** + * The docLinks start service from core + */ + docLinks: DocLinksStart; + /** + * The context object specific to where the editor is currently being consumed + */ + ctx: FieldEditorContext; + /** + * Optional field to edit + */ + field?: IndexPatternField; + /** + * Services + */ + indexPatternService: DataPublicPluginStart['indexPatterns']; + notifications: NotificationsStart; + fieldFormatEditors: PluginStart['fieldFormatEditors']; + fieldFormats: DataPublicPluginStart['fieldFormats']; + uiSettings: CoreStart['uiSettings']; + usageCollection: UsageCollectionStart; +} + +/** + * The container component will be in charge of the communication with the index pattern service + * to retrieve/save the field in the saved object. + * The component is the presentational component that won't know + * anything about where a field comes from and where it should be persisted. + */ + +export const FieldEditorFlyoutContentContainer = ({ + field, + onSave, + onCancel, + docLinks, + indexPatternService, + ctx: { indexPattern, fieldTypeToProcess, search }, + notifications, + fieldFormatEditors, + fieldFormats, + uiSettings, + usageCollection, +}: Props) => { + const fieldToEdit = deserializeField(indexPattern, field); + const [Editor, setEditor] = useState | null>(null); + const [isSaving, setIsSaving] = useState(false); + + const saveField = useCallback( + async (updatedField: Field) => { + setIsSaving(true); + + const { script } = updatedField; + + if (fieldTypeToProcess === 'runtime') { + try { + usageCollection.reportUiCounter( + pluginName, + usageCollection.METRIC_TYPE.COUNT, + 'save_runtime' + ); + // eslint-disable-next-line no-empty + } catch {} + // rename an existing runtime field + if (field?.name && field.name !== updatedField.name) { + indexPattern.removeRuntimeField(field.name); + } + + indexPattern.addRuntimeField(updatedField.name, { + type: updatedField.type as RuntimeType, + script, + }); + } else { + try { + usageCollection.reportUiCounter( + pluginName, + usageCollection.METRIC_TYPE.COUNT, + 'save_concrete' + ); + // eslint-disable-next-line no-empty + } catch {} + } + + const editedField = indexPattern.getFieldByName(updatedField.name); + + try { + if (!editedField) { + throw new Error( + `Unable to find field named '${updatedField.name}' on index pattern '${indexPattern.title}'` + ); + } + + indexPattern.setFieldCustomLabel(updatedField.name, updatedField.customLabel); + editedField.count = updatedField.popularity || 0; + if (updatedField.format) { + indexPattern.setFieldFormat(updatedField.name, updatedField.format); + } else { + indexPattern.deleteFieldFormat(updatedField.name); + } + + await indexPatternService.updateSavedObject(indexPattern).then(() => { + const message = i18n.translate('indexPatternFieldEditor.deleteField.savedHeader', { + defaultMessage: "Saved '{fieldName}'", + values: { fieldName: updatedField.name }, + }); + notifications.toasts.addSuccess(message); + setIsSaving(false); + onSave(editedField); + }); + } catch (e) { + const title = i18n.translate('indexPatternFieldEditor.save.errorTitle', { + defaultMessage: 'Failed to save field changes', + }); + notifications.toasts.addError(e, { title }); + setIsSaving(false); + } + }, + [ + onSave, + indexPattern, + indexPatternService, + notifications, + fieldTypeToProcess, + field?.name, + usageCollection, + ] + ); + + const validateRuntimeField = useMemo(() => getRuntimeFieldValidator(indexPattern.title, search), [ + search, + indexPattern, + ]); + + const loadEditor = useCallback(async () => { + const { FieldEditor } = await import('./field_editor'); + + setEditor(() => FieldEditor); + }, []); + + useEffect(() => { + // On mount: load the editor asynchronously + loadEditor(); + }, [loadEditor]); + + return ( + + ); +}; diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/__snapshots__/format_editor.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/__snapshots__/format_editor.test.tsx.snap new file mode 100644 index 00000000000000..82d21eb5d30ada --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/__snapshots__/format_editor.test.tsx.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FieldFormatEditor should render normally 1`] = ` + + + +`; + +exports[`FieldFormatEditor should render nothing if there is no editor for the format 1`] = ` + + + +`; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap similarity index 92% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap index 69ea6c481d49b1..0f35267e1fb387 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap @@ -16,7 +16,7 @@ exports[`BytesFormatEditor should render normally 1`] = ` >   @@ -30,7 +30,7 @@ exports[`BytesFormatEditor should render normally 1`] = ` label={ diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/bytes/bytes.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/bytes.test.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/bytes/bytes.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/bytes.test.tsx diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/bytes/bytes.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/bytes.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/bytes/bytes.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/bytes.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/bytes/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/bytes/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/color/__snapshots__/color.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/color/__snapshots__/color.test.tsx.snap similarity index 87% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/color/__snapshots__/color.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/color/__snapshots__/color.test.tsx.snap index c66e7789aa511e..c33bb57bfeac89 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/color/__snapshots__/color.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/color/__snapshots__/color.test.tsx.snap @@ -9,7 +9,7 @@ exports[`ColorFormatEditor should render multiple colors 1`] = ` "field": "regex", "name": , "render": [Function], @@ -18,7 +18,7 @@ exports[`ColorFormatEditor should render multiple colors 1`] = ` "field": "text", "name": , "render": [Function], @@ -27,7 +27,7 @@ exports[`ColorFormatEditor should render multiple colors 1`] = ` "field": "background", "name": , "render": [Function], @@ -35,7 +35,7 @@ exports[`ColorFormatEditor should render multiple colors 1`] = ` Object { "name": , "render": [Function], @@ -89,7 +89,7 @@ exports[`ColorFormatEditor should render multiple colors 1`] = ` > @@ -108,7 +108,7 @@ exports[`ColorFormatEditor should render other type normally (range field) 1`] = "field": "range", "name": , "render": [Function], @@ -117,7 +117,7 @@ exports[`ColorFormatEditor should render other type normally (range field) 1`] = "field": "text", "name": , "render": [Function], @@ -126,7 +126,7 @@ exports[`ColorFormatEditor should render other type normally (range field) 1`] = "field": "background", "name": , "render": [Function], @@ -134,7 +134,7 @@ exports[`ColorFormatEditor should render other type normally (range field) 1`] = Object { "name": , "render": [Function], @@ -181,7 +181,7 @@ exports[`ColorFormatEditor should render other type normally (range field) 1`] = > @@ -200,7 +200,7 @@ exports[`ColorFormatEditor should render string type normally (regex field) 1`] "field": "regex", "name": , "render": [Function], @@ -209,7 +209,7 @@ exports[`ColorFormatEditor should render string type normally (regex field) 1`] "field": "text", "name": , "render": [Function], @@ -218,7 +218,7 @@ exports[`ColorFormatEditor should render string type normally (regex field) 1`] "field": "background", "name": , "render": [Function], @@ -226,7 +226,7 @@ exports[`ColorFormatEditor should render string type normally (regex field) 1`] Object { "name": , "render": [Function], @@ -273,7 +273,7 @@ exports[`ColorFormatEditor should render string type normally (regex field) 1`] > diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/color/color.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/color/color.test.tsx similarity index 96% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/color/color.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/color/color.test.tsx index f0e7d4aea42c84..1026012f3b8878 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/color/color.test.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/color/color.test.tsx @@ -11,7 +11,7 @@ import { shallowWithI18nProvider } from '@kbn/test/jest'; import { FieldFormat } from 'src/plugins/data/public'; import { ColorFormatEditor } from './color'; -import { fieldFormats } from '../../../../../../../../data/public'; +import { fieldFormats } from '../../../../../../data/public'; const fieldType = 'string'; const format = { diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/color/color.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/color/color.tsx similarity index 89% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/color/color.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/color/color.tsx index b169624fce9080..1e899a7179554f 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/color/color.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/color/color.tsx @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { DefaultFormatEditor, FormatEditorProps } from '../default'; -import { fieldFormats } from '../../../../../../../../../plugins/data/public'; +import { fieldFormats } from '../../../../../../data/public'; interface Color { range?: string; @@ -86,7 +86,7 @@ export class ColorFormatEditor extends DefaultFormatEditor ), @@ -110,7 +110,7 @@ export class ColorFormatEditor extends DefaultFormatEditor ), @@ -134,7 +134,7 @@ export class ColorFormatEditor extends DefaultFormatEditor ), @@ -158,7 +158,7 @@ export class ColorFormatEditor extends DefaultFormatEditor ), @@ -181,7 +181,7 @@ export class ColorFormatEditor extends DefaultFormatEditor ), @@ -200,15 +200,15 @@ export class ColorFormatEditor extends DefaultFormatEditor { @@ -229,7 +229,7 @@ export class ColorFormatEditor extends DefaultFormatEditor diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/color/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/color/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/color/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/color/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/__snapshots__/date.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date/__snapshots__/date.test.tsx.snap similarity index 93% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/__snapshots__/date.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date/__snapshots__/date.test.tsx.snap index 48a7c3e013b1af..4560904c9b4c41 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/__snapshots__/date.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date/__snapshots__/date.test.tsx.snap @@ -16,7 +16,7 @@ exports[`DateFormatEditor should render normally 1`] = ` >   @@ -30,7 +30,7 @@ exports[`DateFormatEditor should render normally 1`] = ` label={ diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/date.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date/date.test.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/date.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date/date.test.tsx diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/date.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date/date.tsx similarity index 94% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/date.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date/date.tsx index ae29c7a1236f57..62fb08855ce93e 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/date.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date/date.tsx @@ -41,7 +41,7 @@ export class DateFormatEditor extends DefaultFormatEditor{defaultPattern}, @@ -54,7 +54,7 @@ export class DateFormatEditor extends DefaultFormatEditor   diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/__snapshots__/date_nanos.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date_nanos/__snapshots__/date_nanos.test.tsx.snap similarity index 93% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/__snapshots__/date_nanos.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date_nanos/__snapshots__/date_nanos.test.tsx.snap index 540c8ece9e35b9..0d0962a281950c 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/__snapshots__/date_nanos.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date_nanos/__snapshots__/date_nanos.test.tsx.snap @@ -16,7 +16,7 @@ exports[`DateFormatEditor should render normally 1`] = ` >   @@ -30,7 +30,7 @@ exports[`DateFormatEditor should render normally 1`] = ` label={ diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/date_nanos.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date_nanos/date_nanos.test.tsx similarity index 95% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/date_nanos.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date_nanos/date_nanos.test.tsx index 3f66dc59ab5697..4e8d56f91c6eb1 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/date_nanos.test.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date_nanos/date_nanos.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { FieldFormat } from '../../../../../../../../data/public'; +import type { FieldFormat } from 'src/plugins/data/public'; import { DateNanosFormatEditor } from './date_nanos'; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/date_nanos.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date_nanos/date_nanos.tsx similarity index 94% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/date_nanos.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date_nanos/date_nanos.tsx index fab96322f0a16d..d9ee099aaef36a 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/date_nanos.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date_nanos/date_nanos.tsx @@ -40,7 +40,7 @@ export class DateNanosFormatEditor extends DefaultFormatEditor{defaultPattern}, @@ -53,7 +53,7 @@ export class DateNanosFormatEditor extends DefaultFormatEditor   diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date_nanos/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/date_nanos/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/date_nanos/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/__snapshots__/default.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/__snapshots__/default.test.tsx.snap similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/__snapshots__/default.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/__snapshots__/default.test.tsx.snap diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/default.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/default.test.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/default.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/default.test.tsx diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/default.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/default.tsx similarity index 92% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/default.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/default.tsx index 1bfc2c2fa340e1..06f3b318b6e935 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/default.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/default.tsx @@ -10,8 +10,8 @@ import React, { PureComponent, ReactText } from 'react'; import { i18n } from '@kbn/i18n'; import { FieldFormat, FieldFormatsContentType } from 'src/plugins/data/public'; -import { Sample } from '../../../../types'; -import { FieldFormatEditorProps } from '../../field_format_editor'; +import { Sample } from '../../types'; +import { FormatSelectEditorProps } from '../../field_format_editor'; export type ConverterParams = string | number | Array; @@ -30,7 +30,7 @@ export const convertSampleInput = ( }; }); } catch (e) { - error = i18n.translate('indexPatternManagement.defaultErrorMessage', { + error = i18n.translate('indexPatternFieldEditor.defaultErrorMessage', { defaultMessage: 'An error occurred while trying to use this format configuration: {message}', values: { message: e.message }, }); @@ -51,7 +51,7 @@ export interface FormatEditorProps

{ format: FieldFormat; formatParams: { type?: string } & P; onChange: (newParams: Record) => void; - onError: FieldFormatEditorProps['onError']; + onError: FormatSelectEditorProps['onError']; } export interface FormatEditorState { diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/default/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/default/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/__snapshots__/duration.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/duration/__snapshots__/duration.test.tsx.snap similarity index 93% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/__snapshots__/duration.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/duration/__snapshots__/duration.test.tsx.snap index c617c3b43039bf..cb7949deda64f6 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/__snapshots__/duration.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/duration/__snapshots__/duration.test.tsx.snap @@ -12,7 +12,7 @@ exports[`DurationFormatEditor should render human readable output normally 1`] = label={ } @@ -42,7 +42,7 @@ exports[`DurationFormatEditor should render human readable output normally 1`] = label={ } @@ -124,7 +124,7 @@ exports[`DurationFormatEditor should render non-human readable output normally 1 label={ } @@ -154,7 +154,7 @@ exports[`DurationFormatEditor should render non-human readable output normally 1 label={ } @@ -189,7 +189,7 @@ exports[`DurationFormatEditor should render non-human readable output normally 1 label={ } @@ -216,7 +216,7 @@ exports[`DurationFormatEditor should render non-human readable output normally 1 label={ } diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/duration.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/duration/duration.test.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/duration.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/duration/duration.test.tsx diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/duration.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/duration/duration.tsx similarity index 93% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/duration.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/duration/duration.tsx index 4842c7066a2ef8..de413d02c5011c 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/duration.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/duration/duration.tsx @@ -65,7 +65,7 @@ export class DurationFormatEditor extends DefaultFormatEditor< !(nextProps.format as DurationFormat).isHuman() && nextProps.formatParams.outputPrecision > 20 ) { - error = i18n.translate('indexPatternManagement.durationErrorMessage', { + error = i18n.translate('indexPatternFieldEditor.durationErrorMessage', { defaultMessage: 'Decimal places must be between 0 and 20', }); nextProps.onError(error); @@ -91,7 +91,7 @@ export class DurationFormatEditor extends DefaultFormatEditor< } @@ -115,7 +115,7 @@ export class DurationFormatEditor extends DefaultFormatEditor< } @@ -140,7 +140,7 @@ export class DurationFormatEditor extends DefaultFormatEditor< } @@ -163,7 +163,7 @@ export class DurationFormatEditor extends DefaultFormatEditor< } diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/index.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/duration/index.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/duration/index.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/duration/index.tsx diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap similarity index 92% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap index c73b5e7186547f..3cac3850548351 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap @@ -16,7 +16,7 @@ exports[`NumberFormatEditor should render normally 1`] = ` >   @@ -30,7 +30,7 @@ exports[`NumberFormatEditor should render normally 1`] = ` label={ diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/number.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/number.test.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/number.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/number.test.tsx diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/number.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/number.tsx similarity index 94% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/number.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/number.tsx index 250bbe570a9c4f..2aeb90373bfaba 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/number/number.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/number.tsx @@ -36,7 +36,7 @@ export class NumberFormatEditor extends DefaultFormatEditor{defaultPattern} }} /> @@ -45,7 +45,7 @@ export class NumberFormatEditor extends DefaultFormatEditor   diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/percent/__snapshots__/percent.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/__snapshots__/percent.test.tsx.snap similarity index 92% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/percent/__snapshots__/percent.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/__snapshots__/percent.test.tsx.snap index 16ce8ca9643ef2..f6af1f0dff7fe3 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/percent/__snapshots__/percent.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/__snapshots__/percent.test.tsx.snap @@ -16,7 +16,7 @@ exports[`PercentFormatEditor should render normally 1`] = ` >   @@ -30,7 +30,7 @@ exports[`PercentFormatEditor should render normally 1`] = ` label={ diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/percent/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/percent/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/percent/percent.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/percent.test.tsx similarity index 95% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/percent/percent.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/percent.test.tsx index 6eff2fd279cbb5..072dc0caeb3c8b 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/percent/percent.test.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/percent.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { FieldFormat } from '../../../../../../../../data/public'; +import { FieldFormat } from 'src/plugins/data/public'; import { PercentFormatEditor } from './percent'; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/percent/percent.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/percent.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/percent/percent.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/percent.tsx diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/__snapshots__/static_lookup.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/static_lookup/__snapshots__/static_lookup.test.tsx.snap similarity index 88% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/__snapshots__/static_lookup.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/static_lookup/__snapshots__/static_lookup.test.tsx.snap index 46267b0c3c0e98..c5697cb699eb7a 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/__snapshots__/static_lookup.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/static_lookup/__snapshots__/static_lookup.test.tsx.snap @@ -9,7 +9,7 @@ exports[`StaticLookupFormatEditor should render multiple lookup entries and unkn "field": "key", "name": , "render": [Function], @@ -18,7 +18,7 @@ exports[`StaticLookupFormatEditor should render multiple lookup entries and unkn "field": "value", "name": , "render": [Function], @@ -73,7 +73,7 @@ exports[`StaticLookupFormatEditor should render multiple lookup entries and unkn > @@ -89,7 +89,7 @@ exports[`StaticLookupFormatEditor should render multiple lookup entries and unkn label={ } @@ -116,7 +116,7 @@ exports[`StaticLookupFormatEditor should render normally 1`] = ` "field": "key", "name": , "render": [Function], @@ -125,7 +125,7 @@ exports[`StaticLookupFormatEditor should render normally 1`] = ` "field": "value", "name": , "render": [Function], @@ -174,7 +174,7 @@ exports[`StaticLookupFormatEditor should render normally 1`] = ` > @@ -190,7 +190,7 @@ exports[`StaticLookupFormatEditor should render normally 1`] = ` label={ } diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/static_lookup/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/static_lookup/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/static_lookup.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/static_lookup/static_lookup.test.tsx similarity index 96% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/static_lookup.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/static_lookup/static_lookup.test.tsx index e24b656267d1aa..8d9cb17b33a403 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/static_lookup.test.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/static_lookup/static_lookup.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { shallowWithI18nProvider } from '@kbn/test/jest'; import { StaticLookupFormatEditorFormatParams } from './static_lookup'; -import { FieldFormat } from '../../../../../../../../data/public'; +import { FieldFormat } from 'src/plugins/data/public'; import { StaticLookupFormatEditor } from './static_lookup'; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/static_lookup.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/static_lookup/static_lookup.tsx similarity index 88% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/static_lookup.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/static_lookup/static_lookup.tsx index 8c49615c99f6c2..8ac03bb23bd25a 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/static_lookup/static_lookup.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/static_lookup/static_lookup.tsx @@ -72,7 +72,7 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor ), @@ -96,7 +96,7 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor ), @@ -118,15 +118,15 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor { @@ -148,7 +148,7 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor @@ -156,7 +156,7 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor } @@ -164,7 +164,7 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor } diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/string/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/string/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/string/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/string/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/string/string.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/string/string.test.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/string/string.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/string/string.test.tsx diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/string/string.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/string/string.tsx similarity index 96% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/string/string.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/string/string.tsx index 6f9e0e10e188a4..e86a62775cebcb 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/string/string.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/string/string.tsx @@ -47,7 +47,7 @@ export class StringFormatEditor extends DefaultFormatEditor } diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/__snapshots__/truncate.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/__snapshots__/truncate.test.tsx.snap similarity index 96% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/__snapshots__/truncate.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/__snapshots__/truncate.test.tsx.snap index 2d1ee496d2786b..f982632bba5235 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/__snapshots__/truncate.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/__snapshots__/truncate.test.tsx.snap @@ -12,7 +12,7 @@ exports[`TruncateFormatEditor should render normally 1`] = ` label={ } diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/sample.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/sample.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/sample.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/sample.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/truncate.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/truncate.test.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/truncate.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/truncate.test.tsx diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/truncate.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/truncate.tsx similarity index 96% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/truncate.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/truncate.tsx index 4b24d33e58f3ce..03b7d6e0573cc5 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/truncate/truncate.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/truncate/truncate.tsx @@ -37,7 +37,7 @@ export class TruncateFormatEditor extends DefaultFormatEditor } diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap similarity index 92% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap index b627dbe0576ee2..bc5efb8f5eda42 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap @@ -166,14 +166,26 @@ exports[`UrlFormatEditor should render normally 1`] = ` class="euiFormHelpText euiFormRow__text" id="generated-id-help" > - + + + (opens in a new tab or window) + +

@@ -217,14 +229,26 @@ exports[`UrlFormatEditor should render normally 1`] = ` class="euiFormHelpText euiFormRow__text" id="generated-id-help" > - + + + (opens in a new tab or window) + + diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/url.test.tsx similarity index 75% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/url.test.tsx index 5c86abc3b4a9c1..9f299a433aab1a 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.test.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/url/url.test.tsx @@ -10,8 +10,8 @@ import React from 'react'; import { FieldFormat } from 'src/plugins/data/public'; import { IntlProvider } from 'react-intl'; import { UrlFormatEditor } from './url'; -import { coreMock } from '../../../../../../../../../core/public/mocks'; -import { createKibanaReactContext } from '../../../../../../../../kibana_react/public'; +import { coreMock } from 'src/core/public/mocks'; +import { createKibanaReactContext } from '../../../../../../kibana_react/public'; import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -76,38 +76,6 @@ describe('UrlFormatEditor', () => { expect(container).toMatchSnapshot(); }); - it('should render url template help', async () => { - const { getByText, getByTestId } = renderWithContext( - - ); - - getByText('URL template help'); - userEvent.click(getByText('URL template help')); - expect(getByTestId('urlTemplateFlyoutTestSubj')).toBeVisible(); - }); - - it('should render label template help', async () => { - const { getByText, getByTestId } = renderWithContext( - - ); - - getByText('Label template help'); - userEvent.click(getByText('Label template help')); - expect(getByTestId('labelTemplateFlyoutTestSubj')).toBeVisible(); - }); - it('should render width and height fields if image', async () => { const { getByLabelText } = renderWithContext( { static contextType = contextType; static formatId = 'url'; - // TODO: @kbn/optimizer can't compile this - // declare context: IndexPatternManagmentContextValue; - context: IndexPatternManagmentContextValue | undefined; private get sampleIconPath() { const sampleIconPath = `/plugins/indexPatternManagement/assets/icons/{{value}}.png`; return this.context?.services.http @@ -110,32 +103,6 @@ export class UrlFormatEditor extends DefaultFormatEditor< this.onChange(params); }; - showUrlTemplateHelp = () => { - this.setState({ - showLabelTemplateHelp: false, - showUrlTemplateHelp: true, - }); - }; - - hideUrlTemplateHelp = () => { - this.setState({ - showUrlTemplateHelp: false, - }); - }; - - showLabelTemplateHelp = () => { - this.setState({ - showLabelTemplateHelp: true, - showUrlTemplateHelp: false, - }); - }; - - hideLabelTemplateHelp = () => { - this.setState({ - showLabelTemplateHelp: false, - }); - }; - renderWidthHeightParameters = () => { const width = this.sanitizeNumericValue(this.props.formatParams.width); const height = this.sanitizeNumericValue(this.props.formatParams.height); @@ -143,7 +110,7 @@ export class UrlFormatEditor extends DefaultFormatEditor< + } > + } > - - + } > } @@ -217,9 +179,12 @@ export class UrlFormatEditor extends DefaultFormatEditor< + ) : ( - + ) } checked={!formatParams.openLinkInCurrentTab} @@ -233,14 +198,17 @@ export class UrlFormatEditor extends DefaultFormatEditor< } helpText={ - + @@ -260,14 +228,17 @@ export class UrlFormatEditor extends DefaultFormatEditor< } helpText={ - + diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/field_format_editor.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/field_format_editor.tsx new file mode 100644 index 00000000000000..1f3e87e69fd4c2 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/field_format_editor.tsx @@ -0,0 +1,186 @@ +/* + * 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 React, { PureComponent } from 'react'; +import { EuiCode, EuiFormRow, EuiSelect } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { + FieldFormatInstanceType, + IndexPattern, + KBN_FIELD_TYPES, + ES_FIELD_TYPES, + DataPublicPluginStart, + FieldFormat, +} from 'src/plugins/data/public'; +import { CoreStart } from 'src/core/public'; +import { castEsToKbnFieldTypeName } from '../../../../data/public'; +import { FormatEditor } from './format_editor'; +import { FormatEditorServiceStart } from '../../service'; +import { FieldFormatConfig } from '../../types'; + +export interface FormatSelectEditorProps { + esTypes: ES_FIELD_TYPES[]; + indexPattern: IndexPattern; + fieldFormatEditors: FormatEditorServiceStart['fieldFormatEditors']; + fieldFormats: DataPublicPluginStart['fieldFormats']; + uiSettings: CoreStart['uiSettings']; + onChange: (change?: FieldFormatConfig) => void; + onError: (error?: string) => void; + value?: FieldFormatConfig; +} + +interface FieldTypeFormat { + id: string; + title: string; +} + +export interface FormatSelectEditorState { + fieldTypeFormats: FieldTypeFormat[]; + fieldFormatId?: string; + fieldFormatParams?: { [key: string]: unknown }; + format: FieldFormat; + kbnType: KBN_FIELD_TYPES; +} + +interface InitialFieldTypeFormat extends FieldTypeFormat { + defaultFieldFormat: FieldFormatInstanceType; +} + +const getFieldTypeFormatsList = ( + fieldType: KBN_FIELD_TYPES, + defaultFieldFormat: FieldFormatInstanceType, + fieldFormats: DataPublicPluginStart['fieldFormats'] +) => { + const formatsByType = fieldFormats.getByFieldType(fieldType).map(({ id, title }) => ({ + id, + title, + })); + + return [ + { + id: '', + defaultFieldFormat, + title: i18n.translate('indexPatternFieldEditor.defaultFormatDropDown', { + defaultMessage: '- Default -', + }), + }, + ...formatsByType, + ]; +}; + +export class FormatSelectEditor extends PureComponent< + FormatSelectEditorProps, + FormatSelectEditorState +> { + constructor(props: FormatSelectEditorProps) { + super(props); + const { fieldFormats, esTypes, value } = props; + const kbnType = castEsToKbnFieldTypeName(esTypes[0] || 'keyword'); + + // get current formatter for field, provides default if none exists + const format = value?.id + ? fieldFormats.getInstance(value?.id, value?.params) + : fieldFormats.getDefaultInstance(kbnType, esTypes); + + this.state = { + fieldTypeFormats: getFieldTypeFormatsList( + kbnType, + fieldFormats.getDefaultType(kbnType, esTypes) as FieldFormatInstanceType, + fieldFormats + ), + format, + kbnType, + }; + } + onFormatChange = (formatId: string, params?: any) => { + const { fieldTypeFormats } = this.state; + const { fieldFormats, uiSettings } = this.props; + + const FieldFormatClass = fieldFormats.getType( + formatId || (fieldTypeFormats[0] as InitialFieldTypeFormat).defaultFieldFormat.id + ) as FieldFormatInstanceType; + + const newFormat = new FieldFormatClass(params, (key: string) => uiSettings.get(key)); + + this.setState( + { + fieldFormatId: formatId, + fieldFormatParams: params, + format: newFormat, + }, + () => { + this.props.onChange( + formatId + ? { + id: formatId, + params: params || {}, + } + : undefined + ); + } + ); + }; + onFormatParamsChange = (newParams: { fieldType: string; [key: string]: any }) => { + const { fieldFormatId } = this.state; + this.onFormatChange(fieldFormatId as string, newParams); + }; + + render() { + const { fieldFormatEditors, onError, value } = this.props; + const fieldFormatId = value?.id; + const fieldFormatParams = value?.params; + const { kbnType } = this.state; + + const { fieldTypeFormats, format } = this.state; + + const defaultFormat = (fieldTypeFormats[0] as InitialFieldTypeFormat).defaultFieldFormat.title; + + const label = defaultFormat ? ( + {defaultFormat}, + }} + /> + ) : ( + + ); + return ( + <> + + { + return { value: fmt.id || '', text: fmt.title }; + })} + data-test-subj="editorSelectedFormatId" + onChange={(e) => { + this.onFormatChange(e.target.value); + }} + /> + + {fieldFormatId ? ( + { + this.onFormatChange(fieldFormatId, params); + }} + onError={onError} + /> + ) : null} + + ); + } +} diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/format_editor.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/format_editor.test.tsx new file mode 100644 index 00000000000000..5514baf43ecfc0 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/format_editor.test.tsx @@ -0,0 +1,63 @@ +/* + * 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 React, { PureComponent } from 'react'; +import { shallow } from 'enzyme'; +import { FormatEditor } from './format_editor'; + +class TestEditor extends PureComponent { + render() { + if (this.props) { + return null; + } + return
Test editor
; + } +} + +const formatEditors = { + byFormatId: { + ip: TestEditor, + number: TestEditor, + }, + getById: jest.fn(() => TestEditor as any), + getAll: jest.fn(), +}; + +describe('FieldFormatEditor', () => { + it('should render normally', async () => { + const component = shallow( + {}} + onError={() => {}} + /> + ); + + expect(component).toMatchSnapshot(); + }); + + it('should render nothing if there is no editor for the format', async () => { + const component = shallow( + {}} + onError={() => {}} + /> + ); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/format_editor.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/format_editor.tsx new file mode 100644 index 00000000000000..043a911e69812a --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/format_editor.tsx @@ -0,0 +1,67 @@ +/* + * 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 React, { PureComponent, Fragment } from 'react'; +import { FieldFormat } from 'src/plugins/data/public'; + +export interface FormatEditorProps { + fieldType: string; + fieldFormat: FieldFormat; + fieldFormatId: string; + fieldFormatParams: { [key: string]: unknown }; + fieldFormatEditors: any; + onChange: (change: { fieldType: string; [key: string]: any }) => void; + onError: (error?: string) => void; +} + +interface EditorComponentProps { + fieldType: FormatEditorProps['fieldType']; + format: FormatEditorProps['fieldFormat']; + formatParams: FormatEditorProps['fieldFormatParams']; + onChange: FormatEditorProps['onChange']; + onError: FormatEditorProps['onError']; +} + +interface FormatEditorState { + EditorComponent: React.FC; + fieldFormatId?: string; +} + +export class FormatEditor extends PureComponent { + constructor(props: FormatEditorProps) { + super(props); + this.state = { + EditorComponent: props.fieldFormatEditors.getById(props.fieldFormatId), + }; + } + + static getDerivedStateFromProps(nextProps: FormatEditorProps) { + return { + EditorComponent: nextProps.fieldFormatEditors.getById(nextProps.fieldFormatId) || null, + }; + } + + render() { + const { EditorComponent } = this.state; + const { fieldType, fieldFormat, fieldFormatParams, onChange, onError } = this.props; + + return ( + + {EditorComponent ? ( + + ) : null} + + ); + } +} diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/index.ts new file mode 100644 index 00000000000000..34619f53e9eed6 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/index.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 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 { FormatSelectEditor, FormatSelectEditorProps } from './field_format_editor'; +export * from './editors'; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/__snapshots__/samples.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/__snapshots__/samples.test.tsx.snap similarity index 96% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/__snapshots__/samples.test.tsx.snap rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/__snapshots__/samples.test.tsx.snap index ce8c9e70433c84..1a0b96c14fe359 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/__snapshots__/samples.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/__snapshots__/samples.test.tsx.snap @@ -10,7 +10,7 @@ exports[`FormatEditorSamples should render normally 1`] = ` label={ } diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/index.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/index.ts rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/index.ts diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/samples.scss b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/samples.scss similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/samples.scss rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/samples.scss diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/samples.test.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/samples.test.tsx similarity index 100% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/samples.test.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/samples.test.tsx diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/samples.tsx b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/samples.tsx similarity index 87% rename from src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/samples.tsx rename to src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/samples.tsx index 536cb3567017a9..73727119f10777 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/samples/samples.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/samples/samples.tsx @@ -14,7 +14,7 @@ import { EuiBasicTable, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Sample } from '../../../types'; +import { Sample } from '../types'; interface FormatEditorSamplesProps { samples: Sample[]; @@ -32,7 +32,7 @@ export class FormatEditorSamples extends PureComponent const columns = [ { field: 'input', - name: i18n.translate('indexPatternManagement.samples.inputHeader', { + name: i18n.translate('indexPatternFieldEditor.samples.inputHeader', { defaultMessage: 'Input', }), render: (input: {} | string) => { @@ -41,7 +41,7 @@ export class FormatEditorSamples extends PureComponent }, { field: 'output', - name: i18n.translate('indexPatternManagement.samples.outputHeader', { + name: i18n.translate('indexPatternFieldEditor.samples.outputHeader', { defaultMessage: 'Output', }), render: (output: string) => { @@ -63,7 +63,7 @@ export class FormatEditorSamples extends PureComponent return samples.length ? ( + } > diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/types.ts b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/types.ts new file mode 100644 index 00000000000000..11c0b8a6259070 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/types.ts @@ -0,0 +1,14 @@ +/* + * 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 { ReactText } from 'react'; + +export interface Sample { + input: ReactText | ReactText[]; + output: string; +} diff --git a/src/plugins/index_pattern_field_editor/public/components/index.ts b/src/plugins/index_pattern_field_editor/public/components/index.ts new file mode 100644 index 00000000000000..0fbb574a6f0a41 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/components/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { + FieldEditorFlyoutContent, + Props as FieldEditorFlyoutContentProps, +} from './field_editor_flyout_content'; + +export { + FieldEditorFlyoutContentContainer, + Props as FieldEditorFlyoutContentContainerProps, + FieldEditorContext, +} from './field_editor_flyout_content_container'; + +export * from './field_format_editor'; diff --git a/src/plugins/index_pattern_field_editor/public/constants.ts b/src/plugins/index_pattern_field_editor/public/constants.ts new file mode 100644 index 00000000000000..69d231f3758486 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/constants.ts @@ -0,0 +1,9 @@ +/* + * 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 pluginName = 'index_pattern_field_editor'; diff --git a/src/plugins/index_pattern_field_editor/public/index.ts b/src/plugins/index_pattern_field_editor/public/index.ts new file mode 100644 index 00000000000000..38735013d576d5 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/index.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +/** + * Management Plugin - public + * + * This is the entry point for the entire client-side public contract of the plugin. + * If something is not explicitly exported here, you can safely assume it is private + * to the plugin and not considered stable. + * + * All stateful contracts will be injected by the platform at runtime, and are defined + * in the setup/start interfaces in `plugin.ts`. The remaining items exported here are + * either types, or static code. + */ + +import { IndexPatternFieldEditorPlugin } from './plugin'; + +export { PluginStart as IndexPatternFieldEditorStart } from './types'; +export { DefaultFormatEditor } from './components'; + +export function plugin() { + return new IndexPatternFieldEditorPlugin(); +} + +// Expose types +export type { OpenFieldEditorOptions } from './open_editor'; +export type { FieldEditorContext } from './components/field_editor_flyout_content_container'; diff --git a/src/plugins/index_pattern_field_editor/public/lib/documentation.ts b/src/plugins/index_pattern_field_editor/public/lib/documentation.ts new file mode 100644 index 00000000000000..9577f25184ba0a --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/lib/documentation.ts @@ -0,0 +1,21 @@ +/* + * 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 { DocLinksStart } from 'src/core/public'; + +export const getLinks = (docLinks: DocLinksStart) => { + const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docLinks; + const docsBase = `${ELASTIC_WEBSITE_URL}guide/en`; + const esDocsBase = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}`; + const painlessDocsBase = `${docsBase}/elasticsearch/painless/${DOC_LINK_VERSION}`; + + return { + runtimePainless: `${esDocsBase}/runtime.html#runtime-mapping-fields`, + painlessSyntax: `${painlessDocsBase}/painless-lang-spec.html`, + }; +}; diff --git a/src/plugins/index_pattern_field_editor/public/lib/index.ts b/src/plugins/index_pattern_field_editor/public/lib/index.ts new file mode 100644 index 00000000000000..5d5b3d881e9769 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/lib/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { deserializeField } from './serialization'; + +export { getLinks } from './documentation'; + +export { getRuntimeFieldValidator, RuntimeFieldPainlessError } from './runtime_field_validation'; diff --git a/src/plugins/index_pattern_field_editor/public/lib/runtime_field_validation.test.ts b/src/plugins/index_pattern_field_editor/public/lib/runtime_field_validation.test.ts new file mode 100644 index 00000000000000..b25d47b3d0d151 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/lib/runtime_field_validation.test.ts @@ -0,0 +1,165 @@ +/* + * 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 { dataPluginMock } from '../../../data/public/mocks'; +import { getRuntimeFieldValidator } from './runtime_field_validation'; + +const dataStart = dataPluginMock.createStartContract(); +const { search } = dataStart; + +const runtimeField = { + type: 'keyword', + script: { + source: 'emit("hello")', + }, +}; + +const spy = jest.fn(); + +search.search = () => + ({ + toPromise: spy, + } as any); + +const validator = getRuntimeFieldValidator('myIndex', search); + +describe('Runtime field validation', () => { + const expectedError = { + message: 'Error compiling the painless script', + position: { offset: 4, start: 0, end: 18 }, + reason: 'cannot resolve symbol [emit]', + scriptStack: ["emit.some('value')", ' ^---- HERE'], + }; + + [ + { + title: 'should return null when there are no errors', + response: {}, + status: 200, + expected: null, + }, + { + title: 'should return the error in the first failed shard', + response: { + attributes: { + type: 'status_exception', + reason: 'error while executing search', + caused_by: { + failed_shards: [ + { + shard: 0, + index: 'kibana_sample_data_logs', + node: 'gVwk20UWSdO6VyuNOc_6UA', + reason: { + type: 'script_exception', + script_stack: ["emit.some('value')", ' ^---- HERE'], + position: { offset: 4, start: 0, end: 18 }, + caused_by: { + type: 'illegal_argument_exception', + reason: 'cannot resolve symbol [emit]', + }, + }, + }, + ], + }, + }, + }, + status: 400, + expected: expectedError, + }, + { + title: 'should return the error in the third failed shard', + response: { + attributes: { + type: 'status_exception', + reason: 'error while executing search', + caused_by: { + failed_shards: [ + { + shard: 0, + index: 'kibana_sample_data_logs', + node: 'gVwk20UWSdO6VyuNOc_6UA', + reason: { + type: 'foo', + }, + }, + { + shard: 1, + index: 'kibana_sample_data_logs', + node: 'gVwk20UWSdO6VyuNOc_6UA', + reason: { + type: 'bar', + }, + }, + { + shard: 2, + index: 'kibana_sample_data_logs', + node: 'gVwk20UWSdO6VyuNOc_6UA', + reason: { + type: 'script_exception', + script_stack: ["emit.some('value')", ' ^---- HERE'], + position: { offset: 4, start: 0, end: 18 }, + caused_by: { + type: 'illegal_argument_exception', + reason: 'cannot resolve symbol [emit]', + }, + }, + }, + ], + }, + }, + }, + status: 400, + expected: expectedError, + }, + { + title: 'should have default values if an error prop is not found', + response: { + attributes: { + type: 'status_exception', + reason: 'error while executing search', + caused_by: { + failed_shards: [ + { + shard: 0, + index: 'kibana_sample_data_logs', + node: 'gVwk20UWSdO6VyuNOc_6UA', + reason: { + // script_stack, position and caused_by are missing + type: 'script_exception', + caused_by: { + type: 'illegal_argument_exception', + }, + }, + }, + ], + }, + }, + }, + status: 400, + expected: { + message: 'Error compiling the painless script', + position: null, + reason: null, + scriptStack: [], + }, + }, + ].map(({ title, response, status, expected }) => { + test(title, async () => { + if (status !== 200) { + spy.mockRejectedValueOnce(response); + } else { + spy.mockResolvedValueOnce(response); + } + + const result = await validator(runtimeField); + + expect(result).toEqual(expected); + }); + }); +}); diff --git a/src/plugins/index_pattern_field_editor/public/lib/runtime_field_validation.ts b/src/plugins/index_pattern_field_editor/public/lib/runtime_field_validation.ts new file mode 100644 index 00000000000000..f1a6fd7f9e8aa8 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/lib/runtime_field_validation.ts @@ -0,0 +1,116 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import { DataPublicPluginStart } from '../shared_imports'; +import { EsRuntimeField } from '../types'; + +export interface RuntimeFieldPainlessError { + message: string; + reason: string; + position: { + offset: number; + start: number; + end: number; + } | null; + scriptStack: string[]; +} + +type Error = Record; + +/** + * We are only interested in "script_exception" error type + */ +const getScriptExceptionErrorOnShard = (error: Error): Error | null => { + if (error.type === 'script_exception') { + return error; + } + + if (!error.caused_by) { + return null; + } + + // Recursively try to get a script exception error + return getScriptExceptionErrorOnShard(error.caused_by); +}; + +/** + * We get the first script exception error on any failing shard. + * The UI can only display one error at the time so there is no need + * to look any further. + */ +const getScriptExceptionError = (error: Error): Error | null => { + if (error === undefined || !Array.isArray(error.failed_shards)) { + return null; + } + + let scriptExceptionError = null; + for (const err of error.failed_shards) { + scriptExceptionError = getScriptExceptionErrorOnShard(err.reason); + + if (scriptExceptionError !== null) { + break; + } + } + return scriptExceptionError; +}; + +const parseEsError = (error?: Error): RuntimeFieldPainlessError | null => { + if (error === undefined) { + return null; + } + + const scriptError = getScriptExceptionError(error.caused_by); + + if (scriptError === null) { + return null; + } + + return { + message: i18n.translate( + 'indexPatternFieldEditor.editor.form.scriptEditor.compileErrorMessage', + { + defaultMessage: 'Error compiling the painless script', + } + ), + position: scriptError.position ?? null, + scriptStack: scriptError.script_stack ?? [], + reason: scriptError.caused_by?.reason ?? null, + }; +}; + +/** + * Handler to validate the painless script for syntax and semantic errors. + * This is a temporary solution. In a future work we will have a dedicate + * ES API to debug the script. + */ +export const getRuntimeFieldValidator = ( + index: string, + searchService: DataPublicPluginStart['search'] +) => async (runtimeField: EsRuntimeField) => { + return await searchService + .search({ + params: { + index, + body: { + runtime_mappings: { + temp: runtimeField, + }, + size: 0, + query: { + match_none: {}, + }, + }, + }, + }) + .toPromise() + .then(() => null) + .catch((e) => { + return parseEsError(e.attributes); + }); +}; diff --git a/src/plugins/index_pattern_field_editor/public/lib/serialization.ts b/src/plugins/index_pattern_field_editor/public/lib/serialization.ts new file mode 100644 index 00000000000000..9000a34b23cbea --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/lib/serialization.ts @@ -0,0 +1,28 @@ +/* + * 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 { IndexPatternField, IndexPattern } from '../shared_imports'; +import { Field } from '../types'; + +export const deserializeField = ( + indexPattern: IndexPattern, + field?: IndexPatternField +): Field | undefined => { + if (field === undefined) { + return undefined; + } + + return { + name: field.name, + type: field?.esTypes ? field.esTypes[0] : 'keyword', + script: field.runtimeField ? field.runtimeField.script : undefined, + customLabel: field.customLabel, + popularity: field.count, + format: indexPattern.getFormatterForFieldNoDefault(field.name)?.toJSON(), + }; +}; diff --git a/src/plugins/index_pattern_field_editor/public/mocks.ts b/src/plugins/index_pattern_field_editor/public/mocks.ts new file mode 100644 index 00000000000000..23bd4c385ca3b2 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/mocks.ts @@ -0,0 +1,46 @@ +/* + * 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 { IndexPatternFieldEditorPlugin } from './plugin'; + +export type Start = jest.Mocked< + Omit, 'DeleteRuntimeFieldProvider'> +> & { + DeleteRuntimeFieldProvider: ReturnType< + IndexPatternFieldEditorPlugin['start'] + >['DeleteRuntimeFieldProvider']; +}; + +export type Setup = jest.Mocked>; + +const createSetupContract = (): Setup => { + return { + fieldFormatEditors: { + register: jest.fn(), + } as any, + }; +}; + +const createStartContract = (): Start => { + return { + openEditor: jest.fn(), + fieldFormatEditors: { + getAll: jest.fn(), + getById: jest.fn(), + } as any, + userPermissions: { + editIndexPattern: jest.fn(), + }, + DeleteRuntimeFieldProvider: ({ children }) => children(jest.fn()) as JSX.Element, + }; +}; + +export const indexPatternFieldEditorPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/src/plugins/index_pattern_field_editor/public/open_editor.tsx b/src/plugins/index_pattern_field_editor/public/open_editor.tsx new file mode 100644 index 00000000000000..d4d1f71433ea74 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/open_editor.tsx @@ -0,0 +1,119 @@ +/* + * 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 React from 'react'; +import { CoreStart, OverlayRef } from 'src/core/public'; +import { i18n } from '@kbn/i18n'; + +import { + createKibanaReactContext, + toMountPoint, + IndexPatternField, + DataPublicPluginStart, + IndexPattern, + UsageCollectionStart, +} from './shared_imports'; + +import { InternalFieldType } from './types'; +import { FieldEditorFlyoutContentContainer } from './components/field_editor_flyout_content_container'; + +import { PluginStart } from './types'; + +export interface OpenFieldEditorOptions { + ctx: { + indexPattern: IndexPattern; + }; + onSave?: (field: IndexPatternField) => void; + fieldName?: string; +} + +type CloseEditor = () => void; +interface Dependencies { + core: CoreStart; + /** The search service from the data plugin */ + search: DataPublicPluginStart['search']; + indexPatternService: DataPublicPluginStart['indexPatterns']; + fieldFormats: DataPublicPluginStart['fieldFormats']; + fieldFormatEditors: PluginStart['fieldFormatEditors']; + usageCollection: UsageCollectionStart; +} + +export const getFieldEditorOpener = ({ + core, + indexPatternService, + fieldFormats, + fieldFormatEditors, + search, + usageCollection, +}: Dependencies) => (options: OpenFieldEditorOptions): CloseEditor => { + const { uiSettings, overlays, docLinks, notifications } = core; + const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ + uiSettings, + docLinks, + http: core.http, + }); + + let overlayRef: OverlayRef | null = null; + + const openEditor = ({ onSave, fieldName, ctx }: OpenFieldEditorOptions): CloseEditor => { + const closeEditor = () => { + if (overlayRef) { + overlayRef.close(); + overlayRef = null; + } + }; + + const onSaveField = (updatedField: IndexPatternField) => { + closeEditor(); + + if (onSave) { + onSave(updatedField); + } + }; + + const field = fieldName ? ctx.indexPattern.getFieldByName(fieldName) : undefined; + + if (fieldName && !field) { + const err = i18n.translate('indexPatternFieldEditor.noSuchFieldName', { + defaultMessage: "Field named '{fieldName}' not found on index pattern", + values: { fieldName }, + }); + notifications.toasts.addDanger(err); + return closeEditor; + } + + const isNewRuntimeField = !fieldName; + const isExistingRuntimeField = field && field.runtimeField && !field.isMapped; + const fieldTypeToProcess: InternalFieldType = + isNewRuntimeField || isExistingRuntimeField ? 'runtime' : 'concrete'; + + overlayRef = overlays.openFlyout( + toMountPoint( + + + + ) + ); + + return closeEditor; + }; + + return openEditor(options); +}; diff --git a/src/plugins/index_pattern_field_editor/public/plugin.test.tsx b/src/plugins/index_pattern_field_editor/public/plugin.test.tsx new file mode 100644 index 00000000000000..4870ecc827ab09 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/plugin.test.tsx @@ -0,0 +1,114 @@ +/* + * 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 React from 'react'; + +jest.mock('../../kibana_react/public', () => { + const original = jest.requireActual('../../kibana_react/public'); + + return { + ...original, + toMountPoint: (node: React.ReactNode) => node, + }; +}); + +import { CoreStart } from 'src/core/public'; +import { coreMock } from 'src/core/public/mocks'; +import { dataPluginMock } from '../../data/public/mocks'; +import { usageCollectionPluginMock } from '../../usage_collection/public/mocks'; + +import { registerTestBed } from './test_utils'; + +import { FieldEditorFlyoutContentContainer } from './components/field_editor_flyout_content_container'; +import { IndexPatternFieldEditorPlugin } from './plugin'; + +const noop = () => {}; + +describe('IndexPatternFieldEditorPlugin', () => { + const coreStart: CoreStart = coreMock.createStart(); + const pluginStart = { + data: dataPluginMock.createStartContract(), + usageCollection: usageCollectionPluginMock.createSetupContract(), + }; + + let plugin: IndexPatternFieldEditorPlugin; + + beforeEach(() => { + plugin = new IndexPatternFieldEditorPlugin(); + }); + + test('should expose a handler to open the indexpattern field editor', async () => { + const startApi = await plugin.start(coreStart, pluginStart); + expect(startApi.openEditor).toBeDefined(); + }); + + test('should call core.overlays.openFlyout when opening the editor', async () => { + const openFlyout = jest.fn(); + const onSaveSpy = jest.fn(); + + const coreStartMocked = { + ...coreStart, + overlays: { + ...coreStart.overlays, + openFlyout, + }, + }; + const { openEditor } = await plugin.start(coreStartMocked, pluginStart); + + openEditor({ onSave: onSaveSpy, ctx: { indexPattern: {} as any } }); + + expect(openFlyout).toHaveBeenCalled(); + + const [[arg]] = openFlyout.mock.calls; + expect(arg.props.children.type).toBe(FieldEditorFlyoutContentContainer); + + // We force call the "onSave" prop from the component + // and make sure that the the spy is being called. + // Note: we are testing implementation details, if we change or rename the "onSave" prop on + // the component, we will need to update this test accordingly. + expect(arg.props.children.props.onSave).toBeDefined(); + arg.props.children.props.onSave(); + expect(onSaveSpy).toHaveBeenCalled(); + }); + + test('should return a handler to close the flyout', async () => { + const { openEditor } = await plugin.start(coreStart, pluginStart); + + const closeEditorHandler = openEditor({ onSave: noop, ctx: { indexPattern: {} as any } }); + expect(typeof closeEditorHandler).toBe('function'); + }); + + test('should expose a render props component to delete runtime fields', async () => { + const { DeleteRuntimeFieldProvider } = await plugin.start(coreStart, pluginStart); + + const TestComponent = ({ callback }: { callback: (...args: any[]) => void }) => { + return ( + + {(...args) => { + // Forward arguments passed down to children to our spy callback + callback(args); + return null; + }} + + ); + }; + + const setup = registerTestBed(TestComponent, { + memoryRouter: { wrapComponent: false }, + }); + + const spy = jest.fn(); + // Mount our dummy component and pass it the spy + setup({ callback: spy }); + + expect(spy).toHaveBeenCalled(); + const argumentsFromRenderProps = spy.mock.calls[0][0]; + + expect(argumentsFromRenderProps.length).toBe(1); + expect(typeof argumentsFromRenderProps[0]).toBe('function'); + }); +}); diff --git a/src/plugins/index_pattern_field_editor/public/plugin.ts b/src/plugins/index_pattern_field_editor/public/plugin.ts new file mode 100644 index 00000000000000..c3736b50c344cb --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/plugin.ts @@ -0,0 +1,60 @@ +/* + * 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 { Plugin, CoreSetup, CoreStart } from 'src/core/public'; + +import { PluginSetup, PluginStart, SetupPlugins, StartPlugins } from './types'; +import { getFieldEditorOpener } from './open_editor'; +import { FormatEditorService } from './service'; +import { getDeleteProvider } from './components/delete_field_provider'; + +export class IndexPatternFieldEditorPlugin + implements Plugin { + private readonly formatEditorService = new FormatEditorService(); + + public setup(core: CoreSetup, plugins: SetupPlugins): PluginSetup { + const { fieldFormatEditors } = this.formatEditorService.setup(); + + return { + fieldFormatEditors, + }; + } + + public start(core: CoreStart, plugins: StartPlugins) { + const { fieldFormatEditors } = this.formatEditorService.start(); + const { + application: { capabilities }, + } = core; + const { data, usageCollection } = plugins; + return { + fieldFormatEditors, + openEditor: getFieldEditorOpener({ + core, + indexPatternService: data.indexPatterns, + fieldFormats: data.fieldFormats, + fieldFormatEditors, + search: data.search, + usageCollection, + }), + userPermissions: { + editIndexPattern: () => { + return capabilities.management.kibana.indexPatterns; + }, + }, + DeleteRuntimeFieldProvider: getDeleteProvider( + data.indexPatterns, + usageCollection, + core.notifications + ), + }; + } + + public stop() { + return {}; + } +} diff --git a/src/plugins/index_pattern_management/public/service/field_format_editors/field_format_editors.ts b/src/plugins/index_pattern_field_editor/public/service/field_format_editors/field_format_editors.ts similarity index 89% rename from src/plugins/index_pattern_management/public/service/field_format_editors/field_format_editors.ts rename to src/plugins/index_pattern_field_editor/public/service/field_format_editors/field_format_editors.ts index d5335cdf0f06e7..fdc54a39c8c2a6 100644 --- a/src/plugins/index_pattern_management/public/service/field_format_editors/field_format_editors.ts +++ b/src/plugins/index_pattern_field_editor/public/service/field_format_editors/field_format_editors.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { DefaultFormatEditor } from '../../components/field_editor/components/field_format_editor'; +import { DefaultFormatEditor } from '../../components/field_format_editor'; export class FieldFormatEditors { private editors: Array = []; diff --git a/src/plugins/index_pattern_management/public/service/field_format_editors/index.ts b/src/plugins/index_pattern_field_editor/public/service/field_format_editors/index.ts similarity index 100% rename from src/plugins/index_pattern_management/public/service/field_format_editors/index.ts rename to src/plugins/index_pattern_field_editor/public/service/field_format_editors/index.ts diff --git a/src/plugins/index_pattern_field_editor/public/service/format_editor_service.ts b/src/plugins/index_pattern_field_editor/public/service/format_editor_service.ts new file mode 100644 index 00000000000000..67064e8a9cdbf9 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/service/format_editor_service.ts @@ -0,0 +1,72 @@ +/* + * 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 { FieldFormatEditors } from './field_format_editors'; + +import { + BytesFormatEditor, + ColorFormatEditor, + DateFormatEditor, + DateNanosFormatEditor, + DurationFormatEditor, + NumberFormatEditor, + PercentFormatEditor, + StaticLookupFormatEditor, + StringFormatEditor, + TruncateFormatEditor, + UrlFormatEditor, +} from '../components'; + +/** + * Index patterns management service + * + * @internal + */ +export class FormatEditorService { + fieldFormatEditors: FieldFormatEditors; + + constructor() { + this.fieldFormatEditors = new FieldFormatEditors(); + } + + public setup() { + const defaultFieldFormatEditors = [ + BytesFormatEditor, + ColorFormatEditor, + DateFormatEditor, + DateNanosFormatEditor, + DurationFormatEditor, + NumberFormatEditor, + PercentFormatEditor, + StaticLookupFormatEditor, + StringFormatEditor, + TruncateFormatEditor, + UrlFormatEditor, + ]; + + const fieldFormatEditorsSetup = this.fieldFormatEditors.setup(defaultFieldFormatEditors); + + return { + fieldFormatEditors: fieldFormatEditorsSetup, + }; + } + + public start() { + return { + fieldFormatEditors: this.fieldFormatEditors.start(), + }; + } + + public stop() { + // nothing to do here yet. + } +} + +/** @internal */ +export type FormatEditorServiceSetup = ReturnType; +export type FormatEditorServiceStart = ReturnType; diff --git a/src/plugins/index_pattern_field_editor/public/service/index.ts b/src/plugins/index_pattern_field_editor/public/service/index.ts new file mode 100644 index 00000000000000..700d79459327c1 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/service/index.ts @@ -0,0 +1,9 @@ +/* + * 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 * from './format_editor_service'; diff --git a/src/plugins/index_pattern_field_editor/public/shared_imports.ts b/src/plugins/index_pattern_field_editor/public/shared_imports.ts new file mode 100644 index 00000000000000..9caa5e093a96f1 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/shared_imports.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { + IndexPattern, + IndexPatternField, + DataPublicPluginStart, + FieldFormat, +} from '../../data/public'; + +export { UsageCollectionStart } from '../../usage_collection/public'; + +export { RuntimeType, RuntimeField, KBN_FIELD_TYPES, ES_FIELD_TYPES } from '../../data/common'; + +export { createKibanaReactContext, toMountPoint, CodeEditor } from '../../kibana_react/public'; + +export { + useForm, + useFormData, + useFormContext, + Form, + FormSchema, + UseField, + FormHook, + ValidationFunc, + FieldConfig, +} from '../../es_ui_shared/static/forms/hook_form_lib'; + +export { fieldValidators } from '../../es_ui_shared/static/forms/helpers'; + +export { TextField, ToggleField, NumericField } from '../../es_ui_shared/static/forms/components'; diff --git a/src/plugins/index_pattern_field_editor/public/test_utils/helpers.ts b/src/plugins/index_pattern_field_editor/public/test_utils/helpers.ts new file mode 100644 index 00000000000000..295c32cf28e78d --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/test_utils/helpers.ts @@ -0,0 +1,27 @@ +/* + * 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 { TestBed } from './test_utils'; + +export const getCommonActions = (testBed: TestBed) => { + const toggleFormRow = (row: 'customLabel' | 'value' | 'format', value: 'on' | 'off' = 'on') => { + const testSubj = `${row}Row.toggle`; + const toggle = testBed.find(testSubj); + const isOn = toggle.props()['aria-checked']; + + if ((value === 'on' && isOn) || (value === 'off' && isOn === false)) { + return; + } + + testBed.form.toggleEuiSwitch(testSubj); + }; + + return { + toggleFormRow, + }; +}; diff --git a/src/plugins/index_pattern_field_editor/public/test_utils/index.ts b/src/plugins/index_pattern_field_editor/public/test_utils/index.ts new file mode 100644 index 00000000000000..b5d943281cd79b --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/test_utils/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 * from './test_utils'; + +export * from './mocks'; + +export * from './helpers'; diff --git a/src/plugins/index_pattern_field_editor/public/test_utils/mocks.ts b/src/plugins/index_pattern_field_editor/public/test_utils/mocks.ts new file mode 100644 index 00000000000000..c6bc24f1768588 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/test_utils/mocks.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 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 { DocLinksStart } from 'src/core/public'; + +export const noop = () => {}; + +export const docLinks: DocLinksStart = { + ELASTIC_WEBSITE_URL: 'htts://jestTest.elastic.co', + DOC_LINK_VERSION: 'jest', + links: {} as any, +}; + +// TODO check how we can better stub an index pattern format +export const fieldFormats = { + getDefaultInstance: () => ({ + convert: (val: any) => val, + }), +} as any; diff --git a/src/plugins/index_pattern_field_editor/public/test_utils/setup_environment.tsx b/src/plugins/index_pattern_field_editor/public/test_utils/setup_environment.tsx new file mode 100644 index 00000000000000..885bcc87f89df9 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/test_utils/setup_environment.tsx @@ -0,0 +1,80 @@ +/* + * 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 React from 'react'; + +const EDITOR_ID = 'testEditor'; + +jest.mock('../../../kibana_react/public', () => { + const original = jest.requireActual('../../../kibana_react/public'); + + /** + * We mock the CodeEditor because it requires the + * with the uiSettings passed down. Let's use a simple in our tests. + */ + const CodeEditorMock = (props: any) => { + // Forward our deterministic ID to the consumer + // We need below for the PainlessLang.getSyntaxErrors mock + props.editorDidMount({ + getModel() { + return { + id: EDITOR_ID, + }; + }, + }); + + return ( + ) => { + props.onChange(e.target.value); + }} + /> + ); + }; + + return { + ...original, + CodeEditor: CodeEditorMock, + }; +}); + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + + return { + ...original, + EuiComboBox: (props: any) => ( + { + props.onChange([syntheticEvent['0']]); + }} + /> + ), + }; +}); + +jest.mock('@kbn/monaco', () => { + const original = jest.requireActual('@kbn/monaco'); + + return { + ...original, + PainlessLang: { + ID: 'painless', + getSuggestionProvider: () => undefined, + getSyntaxErrors: () => ({ + [EDITOR_ID]: [], + }), + }, + }; +}); diff --git a/src/plugins/index_pattern_field_editor/public/test_utils/test_utils.ts b/src/plugins/index_pattern_field_editor/public/test_utils/test_utils.ts new file mode 100644 index 00000000000000..c8e4aedc264716 --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/test_utils/test_utils.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { getRandomString } from '@kbn/test/jest'; + +export { registerTestBed, TestBed } from '@kbn/test/jest'; diff --git a/src/plugins/index_pattern_field_editor/public/types.ts b/src/plugins/index_pattern_field_editor/public/types.ts new file mode 100644 index 00000000000000..363af9ceb20fbb --- /dev/null +++ b/src/plugins/index_pattern_field_editor/public/types.ts @@ -0,0 +1,63 @@ +/* + * 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 { FunctionComponent } from 'react'; + +import { + DataPublicPluginStart, + RuntimeField, + RuntimeType, + UsageCollectionStart, +} from './shared_imports'; +import { OpenFieldEditorOptions } from './open_editor'; +import { FormatEditorServiceSetup, FormatEditorServiceStart } from './service'; +import { DeleteProviderProps } from './components/delete_field_provider'; + +export interface PluginSetup { + fieldFormatEditors: FormatEditorServiceSetup['fieldFormatEditors']; +} + +export interface PluginStart { + openEditor(options: OpenFieldEditorOptions): () => void; + fieldFormatEditors: FormatEditorServiceStart['fieldFormatEditors']; + userPermissions: { + editIndexPattern: () => boolean; + }; + DeleteRuntimeFieldProvider: FunctionComponent; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SetupPlugins {} + +export interface StartPlugins { + data: DataPublicPluginStart; + usageCollection: UsageCollectionStart; +} + +export type InternalFieldType = 'concrete' | 'runtime'; + +export interface Field { + name: string; + type: RuntimeField['type'] | string; + script?: RuntimeField['script']; + customLabel?: string; + popularity?: number; + format?: FieldFormatConfig; +} + +export interface FieldFormatConfig { + id: string; + params?: { [key: string]: any }; +} + +export interface EsRuntimeField { + type: RuntimeType | string; + script?: { + source: string; + }; +} diff --git a/src/plugins/index_pattern_field_editor/tsconfig.json b/src/plugins/index_pattern_field_editor/tsconfig.json new file mode 100644 index 00000000000000..559b1aaf0fc26c --- /dev/null +++ b/src/plugins/index_pattern_field_editor/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "public/**/*", + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../data/tsconfig.json" }, + { "path": "../kibana_react/tsconfig.json" }, + { "path": "../kibana_utils/tsconfig.json" }, + { "path": "../es_ui_shared/tsconfig.json" }, + ] +} diff --git a/src/plugins/index_pattern_management/kibana.json b/src/plugins/index_pattern_management/kibana.json index 6c3025485bbd7c..60e382fb395f77 100644 --- a/src/plugins/index_pattern_management/kibana.json +++ b/src/plugins/index_pattern_management/kibana.json @@ -3,6 +3,6 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["management", "data", "urlForwarding"], + "requiredPlugins": ["management", "data", "urlForwarding", "indexPatternFieldEditor"], "requiredBundles": ["kibanaReact", "kibanaUtils"] } diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap index 0e5fc0582f72c5..70b638d5d0b8d4 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap @@ -18,6 +18,8 @@ exports[`CreateIndexPatternWizard renders index pattern step when there are indi
{indexPattern.title} }} + defaultMessage="View and edit fields in {indexPatternTitle}. Field attributes, such as type and searchability, are based on {mappingAPILink} in Elasticsearch." + values={{ + indexPatternTitle: {indexPattern.title}, + mappingAPILink: ( + + {mappingAPILink} + + ), + }} />{' '} - - {mappingAPILink} -

{conflictedFields.length > 0 && ( @@ -203,6 +207,9 @@ export const EditIndexPattern = withRouter( fields={fields} history={history} location={location} + refreshFields={() => { + setFields(indexPattern.getNonScriptedFields()); + }} /> diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap index 8e7fac9c6c1483..6e5e652b8d0eb3 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap @@ -3,6 +3,7 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = `
`; diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx index c5ef40be9c0657..9c154ce1b0e7ba 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx @@ -23,99 +23,84 @@ const items: IndexedFieldItem[] = [ searchable: true, info: [], type: 'name', + kbnType: 'string', excluded: false, format: '', + isMapped: true, }, { name: 'timestamp', displayName: 'timestamp', type: 'date', + kbnType: 'date', info: [], excluded: false, format: 'YYYY-MM-DD', + isMapped: true, }, { name: 'conflictingField', displayName: 'conflictingField', - type: 'conflict', + type: 'text, long', + kbnType: 'conflict', info: [], excluded: false, format: '', + isMapped: true, }, ]; +const renderTable = ( + { editField } = { + editField: () => {}, + } +) => + shallow( +
{}} /> + ); + describe('Table', () => { test('should render normally', () => { - const component = shallow( -
{}} /> - ); - - expect(component).toMatchSnapshot(); + expect(renderTable()).toMatchSnapshot(); }); test('should render normal field name', () => { - const component = shallow( -
{}} /> - ); - - const tableCell = shallow(component.prop('columns')[0].render('Elastic', items[0])); + const tableCell = shallow(renderTable().prop('columns')[0].render('Elastic', items[0])); expect(tableCell).toMatchSnapshot(); }); test('should render timestamp field name', () => { - const component = shallow( -
{}} /> - ); - - const tableCell = shallow(component.prop('columns')[0].render('timestamp', items[1])); + const tableCell = shallow(renderTable().prop('columns')[0].render('timestamp', items[1])); expect(tableCell).toMatchSnapshot(); }); test('should render the boolean template (true)', () => { - const component = shallow( -
{}} /> - ); - - const tableCell = shallow(component.prop('columns')[3].render(true)); + const tableCell = shallow(renderTable().prop('columns')[3].render(true)); expect(tableCell).toMatchSnapshot(); }); test('should render the boolean template (false)', () => { - const component = shallow( -
{}} /> - ); - - const tableCell = shallow(component.prop('columns')[3].render(false, items[2])); + const tableCell = shallow(renderTable().prop('columns')[3].render(false, items[2])); expect(tableCell).toMatchSnapshot(); }); test('should render normal type', () => { - const component = shallow( -
{}} /> - ); - - const tableCell = shallow(component.prop('columns')[1].render('string')); + const tableCell = shallow(renderTable().prop('columns')[1].render('string', {})); expect(tableCell).toMatchSnapshot(); }); test('should render conflicting type', () => { - const component = shallow( -
{}} /> + const tableCell = shallow( + renderTable().prop('columns')[1].render('conflict', { kbnType: 'conflict' }) ); - - const tableCell = shallow(component.prop('columns')[1].render('conflict', true)); expect(tableCell).toMatchSnapshot(); }); test('should allow edits', () => { const editField = jest.fn(); - const component = shallow( -
- ); - // Click the edit button - component.prop('columns')[6].actions[0].onClick(); + renderTable({ editField }).prop('columns')[6].actions[0].onClick(); expect(editField).toBeCalled(); }); }); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx index 58080722d1bced..4e9a2bb6451125 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx @@ -140,6 +140,18 @@ const editDescription = i18n.translate( { defaultMessage: 'Edit' } ); +const deleteLabel = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.table.deleteLabel', + { + defaultMessage: 'Delete', + } +); + +const deleteDescription = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.table.deleteDescription', + { defaultMessage: 'Delete' } +); + const labelDescription = i18n.translate( 'indexPatternManagement.editIndexPattern.fields.table.customLabelTooltip', { defaultMessage: 'A custom label for the field.' } @@ -149,6 +161,7 @@ interface IndexedFieldProps { indexPattern: IIndexPattern; items: IndexedFieldItem[]; editField: (field: IndexedFieldItem) => void; + deleteField: (fieldName: string) => void; } export class Table extends PureComponent { @@ -221,7 +234,7 @@ export class Table extends PureComponent { } render() { - const { items, editField } = this.props; + const { items, editField, deleteField } = this.props; const pagination = { initialPageSize: 10, @@ -245,8 +258,8 @@ export class Table extends PureComponent { name: typeHeader, dataType: 'string', sortable: true, - render: (value: string) => { - return this.renderFieldType(value, value === 'conflict'); + render: (value: string, field: IndexedFieldItem) => { + return this.renderFieldType(value, field.kbnType === 'conflict'); }, 'data-test-subj': 'indexedFieldType', }, @@ -294,10 +307,30 @@ export class Table extends PureComponent { ], width: '40px', }, + { + name: '', + actions: [ + { + name: deleteLabel, + description: deleteDescription, + icon: 'trash', + onClick: (field) => deleteField(field.name), + type: 'icon', + 'data-test-subj': 'deleteField', + available: (field) => !field.isMapped, + }, + ], + width: '40px', + }, ]; return ( - + ); } } diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx index 8786f2f4e8deca..e587ada6695cb4 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx @@ -26,7 +26,8 @@ jest.mock('./components/table', () => ({ })); const helpers = { - redirectToRoute: (obj: any) => {}, + editField: (fieldName: string) => {}, + deleteField: (fieldName: string) => {}, getFieldInfo: () => [], }; @@ -35,7 +36,9 @@ const indexPattern = ({ getFormatterForFieldNoDefault: () => ({ params: () => ({}) }), } as unknown) as IndexPattern; -const mockFieldToIndexPatternField = (spec: Record) => { +const mockFieldToIndexPatternField = ( + spec: Record +) => { return new IndexPatternField((spec as unknown) as IndexPatternField['spec']); }; @@ -44,10 +47,10 @@ const fields = [ name: 'Elastic', displayName: 'Elastic', searchable: true, - type: 'string', + esTypes: ['keyword'], }, - { name: 'timestamp', displayName: 'timestamp', type: 'date' }, - { name: 'conflictingField', displayName: 'conflictingField', type: 'conflict' }, + { name: 'timestamp', displayName: 'timestamp', esTypes: ['date'] }, + { name: 'conflictingField', displayName: 'conflictingField', esTypes: ['keyword', 'long'] }, ].map(mockFieldToIndexPatternField); describe('IndexedFieldsTable', () => { diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 95458b55dbf2a7..c703a882d38d6a 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -18,7 +18,8 @@ interface IndexedFieldsTableProps { fieldFilter?: string; indexedFieldTypeFilter?: string; helpers: { - redirectToRoute: (obj: any) => void; + editField: (fieldName: string) => void; + deleteField: (fieldName: string) => void; getFieldInfo: (indexPattern: IndexPattern, field: IFieldType) => string[]; }; fieldWildcardMatcher: (filters: any[]) => (val: any) => boolean; @@ -60,10 +61,13 @@ export class IndexedFieldsTable extends Component< fields.map((field) => { return { ...field.spec, + type: field.esTypes?.join(', ') || '', + kbnType: field.type, displayName: field.displayName, format: indexPattern.getFormatterForFieldNoDefault(field.name)?.type?.title || '', excluded: fieldWildcardMatch ? fieldWildcardMatch(field.name) : false, info: helpers.getFieldInfo && helpers.getFieldInfo(indexPattern, field), + isMapped: !!field.isMapped, }; })) || [] @@ -102,7 +106,8 @@ export class IndexedFieldsTable extends Component<
this.props.helpers.redirectToRoute(field)} + editField={(field) => this.props.helpers.editField(field.name)} + deleteField={(fieldName) => this.props.helpers.deleteField(fieldName)} /> ); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/types.ts b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/types.ts index dee8f5b0d775f9..47ae84b4d2fd8f 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/types.ts +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/types.ts @@ -11,4 +11,6 @@ import { IFieldType } from '../../../../../../plugins/data/public'; export interface IndexedFieldItem extends IFieldType { info: string[]; excluded: boolean; + kbnType: string; + isMapped: boolean; } diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx index ac57c6ffd78ed9..7771c5d54f4157 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useState, useCallback, useEffect, Fragment, useMemo } from 'react'; +import React, { useState, useCallback, useEffect, Fragment, useMemo, useRef } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { EuiFlexGroup, @@ -17,6 +17,7 @@ import { EuiFieldSearch, EuiSelect, EuiSelectOption, + EuiButton, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { fieldWildcardMatcher } from '../../../../../kibana_utils/public'; @@ -39,6 +40,7 @@ interface TabsProps extends Pick { indexPattern: IndexPattern; fields: IndexPatternField[]; saveIndexPattern: DataPublicPluginStart['indexPatterns']['updateSavedObject']; + refreshFields: () => void; } const searchAriaLabel = i18n.translate( @@ -62,11 +64,26 @@ const filterPlaceholder = i18n.translate( } ); -export function Tabs({ indexPattern, saveIndexPattern, fields, history, location }: TabsProps) { +const addFieldButtonLabel = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.addFieldButtonLabel', + { + defaultMessage: 'Add field', + } +); + +export function Tabs({ + indexPattern, + saveIndexPattern, + fields, + history, + location, + refreshFields, +}: TabsProps) { const { uiSettings, indexPatternManagementStart, docLinks, + indexPatternFieldEditor, } = useKibana().services; const [fieldFilter, setFieldFilter] = useState(''); const [indexedFieldTypeFilter, setIndexedFieldTypeFilter] = useState(''); @@ -76,6 +93,8 @@ export function Tabs({ indexPattern, saveIndexPattern, fields, history, location const [syncingStateFunc, setSyncingStateFunc] = useState({ getCurrentTab: () => TAB_INDEXED_FIELDS, }); + const closeEditorHandler = useRef<() => void | undefined>(); + const { DeleteRuntimeFieldProvider } = indexPatternFieldEditor; const refreshFilters = useCallback(() => { const tempIndexedFieldTypes: string[] = []; @@ -86,7 +105,9 @@ export function Tabs({ indexPattern, saveIndexPattern, fields, history, location tempScriptedFieldLanguages.push(field.lang); } } else { - tempIndexedFieldTypes.push(field.type); + if (field.esTypes) { + tempIndexedFieldTypes.push(field.esTypes?.join(', ')); + } } }); @@ -96,10 +117,36 @@ export function Tabs({ indexPattern, saveIndexPattern, fields, history, location ); }, [indexPattern]); + const closeFieldEditor = useCallback(() => { + if (closeEditorHandler.current) { + closeEditorHandler.current(); + } + }, []); + + const openFieldEditor = useCallback( + (fieldName?: string) => { + closeEditorHandler.current = indexPatternFieldEditor.openEditor({ + ctx: { + indexPattern, + }, + onSave: refreshFields, + fieldName, + }); + }, + [indexPatternFieldEditor, indexPattern, refreshFields] + ); + useEffect(() => { refreshFilters(); }, [indexPattern, indexPattern.fields, refreshFilters]); + useEffect(() => { + return () => { + // When the component unmounts, make sure to close the field editor + closeFieldEditor(); + }; + }, [closeFieldEditor]); + const fieldWildcardMatcherDecorated = useCallback( (filters: string[]) => fieldWildcardMatcher(filters, uiSettings.get(UI_SETTINGS.META_FIELDS)), [uiSettings] @@ -120,15 +167,22 @@ export function Tabs({ indexPattern, saveIndexPattern, fields, history, location /> {type === TAB_INDEXED_FIELDS && indexedFieldTypes.length > 0 && ( - - setIndexedFieldTypeFilter(e.target.value)} - data-test-subj="indexedFieldTypeFilterDropdown" - aria-label={filterAriaLabel} - /> - + <> + + setIndexedFieldTypeFilter(e.target.value)} + data-test-subj="indexedFieldTypeFilterDropdown" + aria-label={filterAriaLabel} + /> + + + openFieldEditor()}> + {addFieldButtonLabel} + + + )} {type === TAB_SCRIPTED_FIELDS && scriptedFieldLanguages.length > 0 && ( @@ -149,6 +203,7 @@ export function Tabs({ indexPattern, saveIndexPattern, fields, history, location indexedFieldTypes, scriptedFieldLanguageFilter, scriptedFieldLanguages, + openFieldEditor, ] ); @@ -161,19 +216,22 @@ export function Tabs({ indexPattern, saveIndexPattern, fields, history, location {getFilterSection(type)} - { - history.push(getPath(field, indexPattern)); - }, - getFieldInfo: indexPatternManagementStart.list.getFieldInfo, - }} - /> + + {(deleteField) => ( + + )} + ); case TAB_SCRIPTED_FIELDS: @@ -227,6 +285,9 @@ export function Tabs({ indexPattern, saveIndexPattern, fields, history, location refreshFilters, scriptedFieldLanguageFilter, saveIndexPattern, + openFieldEditor, + DeleteRuntimeFieldProvider, + refreshFields, ] ); diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/label_template_flyout.test.tsx.snap b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/label_template_flyout.test.tsx.snap deleted file mode 100644 index 38f630358d064c..00000000000000 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/label_template_flyout.test.tsx.snap +++ /dev/null @@ -1,109 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LabelTemplateFlyout should not render if not visible 1`] = `""`; - -exports[`LabelTemplateFlyout should render normally 1`] = ` - - - -

- -

-

- - {{ }} - , - } - } - /> -

-
    -
  • - - value - - —  - -
  • -
  • - - url - - —  - -
  • -
-

- -

- User #1234", - "urlTemplate": "http://company.net/profiles?user_id={{value}}", - }, - Object { - "input": "/assets/main.css", - "labelTemplate": "View Asset", - "output": "View Asset", - "urlTemplate": "http://site.com{{rawValue}}", - }, - ] - } - noItemsMessage="No items found" - responsive={true} - tableLayout="fixed" - /> -
-
-
-`; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.tsx.snap b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.tsx.snap deleted file mode 100644 index 83e815dd72661c..00000000000000 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.tsx.snap +++ /dev/null @@ -1,114 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UrlTemplateFlyout should not render if not visible 1`] = `""`; - -exports[`UrlTemplateFlyout should render normally 1`] = ` - - - -

- -

-

- - {{ }} - , - "strongUrlTemplate": - - , - } - } - /> -

-
    -
  • - - value - - —  - -
  • -
  • - - rawValue - - —  - -
  • -
-

- -

- -
-
-
-`; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.test.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.test.tsx deleted file mode 100644 index 0af6ba062e86b8..00000000000000 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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 React from 'react'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; - -import { LabelTemplateFlyout } from './label_template_flyout'; - -describe('LabelTemplateFlyout', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider(); - expect(component).toMatchSnapshot(); - }); - - it('should not render if not visible', async () => { - const component = shallowWithI18nProvider(); - expect(component).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.tsx deleted file mode 100644 index 0ce1858a5cc44c..00000000000000 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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 React from 'react'; - -import { EuiBasicTable, EuiCode, EuiFlyout, EuiFlyoutBody, EuiText } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -interface LabelTemplateExampleItem { - input: string | number; - urlTemplate: string; - labelTemplate: string; - output: string; -} - -const items: LabelTemplateExampleItem[] = [ - { - input: 1234, - urlTemplate: 'http://company.net/profiles?user_id={{value}}', - labelTemplate: i18n.translate('indexPatternManagement.labelTemplate.example.idLabel', { - defaultMessage: 'User #{value}', - values: { value: '{{value}}' }, - }), - output: - '' + - i18n.translate('indexPatternManagement.labelTemplate.example.output.idLabel', { - defaultMessage: 'User', - }) + - ' #1234', - }, - { - input: '/assets/main.css', - urlTemplate: 'http://site.com{{rawValue}}', - labelTemplate: i18n.translate('indexPatternManagement.labelTemplate.example.pathLabel', { - defaultMessage: 'View Asset', - }), - output: - '' + - i18n.translate('indexPatternManagement.labelTemplate.example.output.pathLabel', { - defaultMessage: 'View Asset', - }) + - '', - }, -]; - -export const LabelTemplateFlyout = ({ isVisible = false, onClose = () => {} }) => { - return isVisible ? ( - - - -

- -

-

- {'{{ }}'} }} - /> -

-
    -
  • - value —  - -
  • -
  • - url —  - -
  • -
-

- -

- - items={items} - columns={[ - { - field: 'input', - name: i18n.translate('indexPatternManagement.labelTemplate.inputHeader', { - defaultMessage: 'Input', - }), - width: '160px', - }, - { - field: 'urlTemplate', - name: i18n.translate('indexPatternManagement.labelTemplate.urlHeader', { - defaultMessage: 'URL Template', - }), - }, - { - field: 'labelTemplate', - name: i18n.translate('indexPatternManagement.labelTemplate.labelHeader', { - defaultMessage: 'Label Template', - }), - }, - { - field: 'output', - name: i18n.translate('indexPatternManagement.labelTemplate.outputHeader', { - defaultMessage: 'Output', - }), - render: (value: LabelTemplateExampleItem['output']) => { - return ( - - ); - }, - }, - ]} - /> -
-
-
- ) : null; -}; - -LabelTemplateFlyout.displayName = 'LabelTemplateFlyout'; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.test.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.test.tsx deleted file mode 100644 index bbdb18da901d1f..00000000000000 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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 React from 'react'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; - -import { UrlTemplateFlyout } from './url_template_flyout'; - -describe('UrlTemplateFlyout', () => { - it('should render normally', async () => { - const component = shallowWithI18nProvider(); - expect(component).toMatchSnapshot(); - }); - - it('should not render if not visible', async () => { - const component = shallowWithI18nProvider(); - expect(component).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.tsx deleted file mode 100644 index fc2b8d72536ec6..00000000000000 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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 React from 'react'; - -import { EuiBasicTable, EuiCode, EuiFlyout, EuiFlyoutBody, EuiText } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -export const UrlTemplateFlyout = ({ isVisible = false, onClose = () => {} }) => { - return isVisible ? ( - - - -

- -

-

- {'{{ }}'}, - strongUrlTemplate: ( - - - - ), - }} - /> -

-
    -
  • - value —  - -
  • -
  • - rawValue —  - -
  • -
-

- -

- -
-
-
- ) : null; -}; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/field_format_editor.test.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/field_format_editor.test.tsx index d9352f18e96731..78dc87f7a8027a 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/field_format_editor.test.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/field_format_editor.test.tsx @@ -10,7 +10,7 @@ import React, { PureComponent } from 'react'; import { shallow } from 'enzyme'; import { FieldFormatEditor } from './field_format_editor'; -import { DefaultFormatEditor } from './editors/default'; +import type { DefaultFormatEditor } from 'src/plugins/index_pattern_field_editor/public'; class TestEditor extends PureComponent { render() { diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/field_format_editor.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/field_format_editor.tsx index 81abf2b5b1d203..60107e19170c77 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/field_format_editor.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/field_format_editor.tsx @@ -7,7 +7,7 @@ */ import React, { PureComponent, Fragment } from 'react'; -import { DefaultFormatEditor } from '../../components/field_format_editor/editors/default'; +import type { DefaultFormatEditor } from 'src/plugins/index_pattern_field_editor/public'; export interface FieldFormatEditorProps { fieldType: string; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/index.ts b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/index.ts index 7eea994a3e2d2f..66d9760b24c657 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/index.ts +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/index.ts @@ -7,4 +7,3 @@ */ export { FieldFormatEditor } from './field_format_editor'; -export * from './editors'; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx index 829536063a26c9..f0da57a5f9b6f3 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx @@ -487,7 +487,7 @@ export class FieldEditor extends PureComponent diff --git a/src/plugins/index_pattern_management/public/index.ts b/src/plugins/index_pattern_management/public/index.ts index 27e405a4113de9..94611705a93908 100644 --- a/src/plugins/index_pattern_management/public/index.ts +++ b/src/plugins/index_pattern_management/public/index.ts @@ -31,6 +31,4 @@ export { IndexPatternListConfig, } from './service'; -export { DefaultFormatEditor } from './components/field_editor/components/field_format_editor'; - export { MlCardState } from './types'; diff --git a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx index 45941969dbed12..e47f60ad6fcdd6 100644 --- a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx +++ b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx @@ -42,7 +42,7 @@ export async function mountManagementSection( ) { const [ { chrome, application, savedObjects, uiSettings, notifications, overlays, http, docLinks }, - { data }, + { data, indexPatternFieldEditor }, indexPatternManagementStart, ] = await getStartServices(); const canSave = Boolean(application.capabilities.indexPatterns.save); @@ -61,9 +61,11 @@ export async function mountManagementSection( http, docLinks, data, + indexPatternFieldEditor, indexPatternManagementStart: indexPatternManagementStart as IndexPatternManagementStart, setBreadcrumbs: params.setBreadcrumbs, getMlCardState, + fieldFormatEditors: indexPatternFieldEditor.fieldFormatEditors, }; ReactDOM.render( diff --git a/src/plugins/index_pattern_management/public/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts index 974b1ae1bd8630..309d5a5611cd62 100644 --- a/src/plugins/index_pattern_management/public/mocks.ts +++ b/src/plugins/index_pattern_management/public/mocks.ts @@ -11,11 +11,13 @@ import { coreMock } from '../../../core/public/mocks'; import { managementPluginMock } from '../../management/public/mocks'; import { urlForwardingPluginMock } from '../../url_forwarding/public/mocks'; import { dataPluginMock } from '../../data/public/mocks'; +import { indexPatternFieldEditorPluginMock } from '../../index_pattern_field_editor/public/mocks'; import { IndexPatternManagementSetup, IndexPatternManagementStart, IndexPatternManagementPlugin, } from './plugin'; +import { IndexPatternManagmentContext } from './types'; const createSetupContract = (): IndexPatternManagementSetup => ({ creation: { @@ -24,10 +26,6 @@ const createSetupContract = (): IndexPatternManagementSetup => ({ list: { addListConfig: jest.fn(), } as any, - fieldFormatEditors: { - getAll: jest.fn(), - getById: jest.fn(), - } as any, environment: { update: jest.fn(), }, @@ -43,10 +41,6 @@ const createStartContract = (): IndexPatternManagementStart => ({ getFieldInfo: jest.fn(), areScriptedFieldsEnabled: jest.fn(), } as any, - fieldFormatEditors: { - getAll: jest.fn(), - getById: jest.fn(), - } as any, }); const createInstance = async () => { @@ -59,6 +53,7 @@ const createInstance = async () => { const doStart = () => plugin.start(coreMock.createStart(), { data: dataPluginMock.createStartContract(), + indexPatternFieldEditor: indexPatternFieldEditorPluginMock.createStartContract(), }); return { @@ -69,13 +64,17 @@ const createInstance = async () => { }; const docLinks = { + ELASTIC_WEBSITE_URL: 'htts://jestTest.elastic.co', + DOC_LINK_VERSION: 'jest', links: { indexPatterns: {}, scriptedFields: {}, - }, + } as any, }; -const createIndexPatternManagmentContext = () => { +const createIndexPatternManagmentContext = (): { + [key in keyof IndexPatternManagmentContext]: any; +} => { const { chrome, application, @@ -86,6 +85,7 @@ const createIndexPatternManagmentContext = () => { } = coreMock.createStart(); const { http } = coreMock.createSetup(); const data = dataPluginMock.createStartContract(); + const indexPatternFieldEditor = indexPatternFieldEditorPluginMock.createStartContract(); return { chrome, @@ -97,8 +97,11 @@ const createIndexPatternManagmentContext = () => { http, docLinks, data, + indexPatternFieldEditor, indexPatternManagementStart: createStartContract(), setBreadcrumbs: () => {}, + getMlCardState: () => 2, + fieldFormatEditors: indexPatternFieldEditor.fieldFormatEditors, }; }; diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts index bc4ef83de5012f..ed92172c8b91ca 100644 --- a/src/plugins/index_pattern_management/public/plugin.ts +++ b/src/plugins/index_pattern_management/public/plugin.ts @@ -17,6 +17,7 @@ import { } from './service'; import { ManagementSetup } from '../../management/public'; +import { IndexPatternFieldEditorStart } from '../../index_pattern_field_editor/public'; export interface IndexPatternManagementSetupDependencies { management: ManagementSetup; @@ -25,6 +26,7 @@ export interface IndexPatternManagementSetupDependencies { export interface IndexPatternManagementStartDependencies { data: DataPublicPluginStart; + indexPatternFieldEditor: IndexPatternFieldEditorStart; } export type IndexPatternManagementSetup = IndexPatternManagementServiceSetup; diff --git a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts index a686891c980149..15be7f11892e49 100644 --- a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts +++ b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts @@ -9,23 +9,7 @@ import { HttpSetup } from '../../../../core/public'; import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation'; import { IndexPatternListManager, IndexPatternListConfig } from './list'; -import { FieldFormatEditors } from './field_format_editors'; import { EnvironmentService } from './environment'; - -import { - BytesFormatEditor, - ColorFormatEditor, - DateFormatEditor, - DateNanosFormatEditor, - DurationFormatEditor, - NumberFormatEditor, - PercentFormatEditor, - StaticLookupFormatEditor, - StringFormatEditor, - TruncateFormatEditor, - UrlFormatEditor, -} from '../components/field_editor/components/field_format_editor'; - interface SetupDependencies { httpClient: HttpSetup; } @@ -38,13 +22,11 @@ interface SetupDependencies { export class IndexPatternManagementService { indexPatternCreationManager: IndexPatternCreationManager; indexPatternListConfig: IndexPatternListManager; - fieldFormatEditors: FieldFormatEditors; environmentService: EnvironmentService; constructor() { this.indexPatternCreationManager = new IndexPatternCreationManager(); this.indexPatternListConfig = new IndexPatternListManager(); - this.fieldFormatEditors = new FieldFormatEditors(); this.environmentService = new EnvironmentService(); } @@ -55,26 +37,9 @@ export class IndexPatternManagementService { const indexPatternListConfigSetup = this.indexPatternListConfig.setup(); indexPatternListConfigSetup.addListConfig(IndexPatternListConfig); - const defaultFieldFormatEditors = [ - BytesFormatEditor, - ColorFormatEditor, - DateFormatEditor, - DateNanosFormatEditor, - DurationFormatEditor, - NumberFormatEditor, - PercentFormatEditor, - StaticLookupFormatEditor, - StringFormatEditor, - TruncateFormatEditor, - UrlFormatEditor, - ]; - - const fieldFormatEditorsSetup = this.fieldFormatEditors.setup(defaultFieldFormatEditors); - return { creation: creationManagerSetup, list: indexPatternListConfigSetup, - fieldFormatEditors: fieldFormatEditorsSetup, environment: this.environmentService.setup(), }; } @@ -83,7 +48,6 @@ export class IndexPatternManagementService { return { creation: this.indexPatternCreationManager.start(), list: this.indexPatternListConfig.start(), - fieldFormatEditors: this.fieldFormatEditors.start(), }; } diff --git a/src/plugins/index_pattern_management/public/types.ts b/src/plugins/index_pattern_management/public/types.ts index 84e8ae007b99c8..62ee18ababc0bd 100644 --- a/src/plugins/index_pattern_management/public/types.ts +++ b/src/plugins/index_pattern_management/public/types.ts @@ -20,6 +20,7 @@ import { DataPublicPluginStart } from 'src/plugins/data/public'; import { ManagementAppMountParams } from '../../management/public'; import { IndexPatternManagementStart } from './index'; import { KibanaReactContextValue } from '../../kibana_react/public'; +import { IndexPatternFieldEditorStart } from '../../index_pattern_field_editor/public'; export interface IndexPatternManagmentContext { chrome: ChromeStart; @@ -31,9 +32,11 @@ export interface IndexPatternManagmentContext { http: HttpSetup; docLinks: DocLinksStart; data: DataPublicPluginStart; + indexPatternFieldEditor: IndexPatternFieldEditorStart; indexPatternManagementStart: IndexPatternManagementStart; setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; getMlCardState: () => MlCardState; + fieldFormatEditors: IndexPatternFieldEditorStart['fieldFormatEditors']; } export type IndexPatternManagmentContextValue = KibanaReactContextValue; diff --git a/src/plugins/index_pattern_management/tsconfig.json b/src/plugins/index_pattern_management/tsconfig.json index 4dca1634fddb69..37bd3e4aa5bbb9 100644 --- a/src/plugins/index_pattern_management/tsconfig.json +++ b/src/plugins/index_pattern_management/tsconfig.json @@ -18,5 +18,7 @@ { "path": "../url_forwarding/tsconfig.json" }, { "path": "../kibana_react/tsconfig.json" }, { "path": "../kibana_utils/tsconfig.json" }, + { "path": "../es_ui_shared/tsconfig.json" }, + { "path": "../index_pattern_field_editor/tsconfig.json" }, ] } diff --git a/test/functional/apps/management/_handle_version_conflict.js b/test/functional/apps/management/_handle_version_conflict.js index 61b143e9874714..16c427e9bbe20b 100644 --- a/test/functional/apps/management/_handle_version_conflict.js +++ b/test/functional/apps/management/_handle_version_conflict.js @@ -18,6 +18,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { + const testSubjects = getService('testSubjects'); const esArchiver = getService('esArchiver'); const browser = getService('browser'); const es = getService('legacyEs'); @@ -65,6 +66,11 @@ export default function ({ getService, getPageObjects }) { log.debug('Starting openControlsByName (' + fieldName + ')'); await PageObjects.settings.openControlsByName(fieldName); log.debug('controls are open'); + await ( + await (await testSubjects.find('formatRow')).findAllByCssSelector( + '[data-test-subj="toggle"]' + ) + )[0].click(); await PageObjects.settings.setFieldFormat('url'); const response = await es.update({ index: '.kibana', diff --git a/test/functional/apps/management/_index_pattern_filter.js b/test/functional/apps/management/_index_pattern_filter.js index eeb0b224d5f0ca..261ba29410a09d 100644 --- a/test/functional/apps/management/_index_pattern_filter.js +++ b/test/functional/apps/management/_index_pattern_filter.js @@ -35,23 +35,23 @@ export default function ({ getService, getPageObjects }) { await PageObjects.settings.clickKibanaIndexPatterns(); await PageObjects.settings.clickIndexPatternLogstash(); await PageObjects.settings.getFieldTypes(); - await PageObjects.settings.setFieldTypeFilter('string'); + await PageObjects.settings.setFieldTypeFilter('keyword'); await retry.try(async function () { const fieldTypes = await PageObjects.settings.getFieldTypes(); expect(fieldTypes.length).to.be.above(0); for (const fieldType of fieldTypes) { - expect(fieldType).to.be('string'); + expect(fieldType).to.be('keyword'); } }); - await PageObjects.settings.setFieldTypeFilter('number'); + await PageObjects.settings.setFieldTypeFilter('long'); await retry.try(async function () { const fieldTypes = await PageObjects.settings.getFieldTypes(); expect(fieldTypes.length).to.be.above(0); for (const fieldType of fieldTypes) { - expect(fieldType).to.be('number'); + expect(fieldType).to.be('long'); } }); }); diff --git a/test/functional/apps/management/_index_pattern_popularity.js b/test/functional/apps/management/_index_pattern_popularity.js index 231617d7084e96..0618dd79e272ec 100644 --- a/test/functional/apps/management/_index_pattern_popularity.js +++ b/test/functional/apps/management/_index_pattern_popularity.js @@ -10,6 +10,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); + const testSubjects = getService('testSubjects'); const log = getService('log'); const PageObjects = getPageObjects(['settings', 'common']); @@ -27,11 +28,12 @@ export default function ({ getService, getPageObjects }) { log.debug('Starting openControlsByName (' + fieldName + ')'); await PageObjects.settings.openControlsByName(fieldName); log.debug('increasePopularity'); + await testSubjects.click('toggleAdvancedSetting'); await PageObjects.settings.increasePopularity(); }); afterEach(async () => { - await PageObjects.settings.controlChangeCancel(); + await testSubjects.click('closeFlyoutButton'); await PageObjects.settings.removeIndexPattern(); // Cancel saving the popularity change (we didn't make a change in this case, just checking the value) }); @@ -44,12 +46,12 @@ export default function ({ getService, getPageObjects }) { it('should be reset on cancel', async function () { // Cancel saving the popularity change - await PageObjects.settings.controlChangeCancel(); + await testSubjects.click('closeFlyoutButton'); await PageObjects.settings.openControlsByName(fieldName); // check that it is 0 (previous increase was cancelled const popularity = await PageObjects.settings.getPopularity(); log.debug('popularity = ' + popularity); - expect(popularity).to.be(''); + expect(popularity).to.be('0'); }); it('can be saved', async function () { diff --git a/test/functional/apps/management/_index_pattern_results_sort.js b/test/functional/apps/management/_index_pattern_results_sort.js index 90af0636bcd486..cedf5ee355b36a 100644 --- a/test/functional/apps/management/_index_pattern_results_sort.js +++ b/test/functional/apps/management/_index_pattern_results_sort.js @@ -17,6 +17,12 @@ export default function ({ getService, getPageObjects }) { before(async function () { // delete .kibana index and then wait for Kibana to re-create it await kibanaServer.uiSettings.replace({}); + await PageObjects.settings.navigateTo(); + await PageObjects.settings.createIndexPattern(); + }); + + after(async function () { + return await PageObjects.settings.removeIndexPattern(); }); const columns = [ @@ -31,8 +37,8 @@ export default function ({ getService, getPageObjects }) { }, { heading: 'Type', - first: '_source', - last: 'string', + first: '', + last: 'text', selector: async function () { const tableRow = await PageObjects.settings.getTableRow(0, 1); return await tableRow.getVisibleText(); @@ -42,16 +48,11 @@ export default function ({ getService, getPageObjects }) { columns.forEach(function (col) { describe('sort by heading - ' + col.heading, function indexPatternCreation() { - before(async function () { - await PageObjects.settings.createIndexPattern(); - }); - - after(async function () { - return await PageObjects.settings.removeIndexPattern(); - }); - it('should sort ascending', async function () { - await PageObjects.settings.sortBy(col.heading); + console.log('col.heading', col.heading); + if (col.heading !== 'Name') { + await PageObjects.settings.sortBy(col.heading); + } const rowText = await col.selector(); expect(rowText).to.be(col.first); }); @@ -65,15 +66,6 @@ export default function ({ getService, getPageObjects }) { }); describe('field list pagination', function () { const EXPECTED_FIELD_COUNT = 86; - - before(async function () { - await PageObjects.settings.createIndexPattern(); - }); - - after(async function () { - return await PageObjects.settings.removeIndexPattern(); - }); - it('makelogs data should have expected number of fields', async function () { await retry.try(async function () { const TabCount = await PageObjects.settings.getFieldsTabCount(); diff --git a/test/functional/apps/visualize/_tag_cloud.ts b/test/functional/apps/visualize/_tag_cloud.ts index e619a35fb3d0b7..c7d864e5cfb233 100644 --- a/test/functional/apps/visualize/_tag_cloud.ts +++ b/test/functional/apps/visualize/_tag_cloud.ts @@ -11,6 +11,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); const filterBar = getService('filterBar'); const log = getService('log'); const inspector = getService('inspector'); @@ -145,6 +146,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.settings.clickIndexPatternLogstash(); await PageObjects.settings.filterField(termsField); await PageObjects.settings.openControlsByName(termsField); + await ( + await (await testSubjects.find('formatRow')).findAllByCssSelector( + '[data-test-subj="toggle"]' + ) + )[0].click(); await PageObjects.settings.setFieldFormat('bytes'); await PageObjects.settings.controlChangeSave(); await PageObjects.common.navigateToApp('visualize'); diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index f59db345f39ff9..09a05732b791bb 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -249,7 +249,7 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider await find.clickByCssSelector( `table.euiTable tbody tr.euiTableRow:nth-child(${tableFields.indexOf(name) + 1}) - td:last-child button` + td:nth-last-child(2) button` ); } diff --git a/tsconfig.json b/tsconfig.json index 48feac3efe4752..f6ce6b92b7e02f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -63,5 +63,6 @@ { "path": "./src/plugins/visualizations/tsconfig.json" }, { "path": "./src/plugins/visualize/tsconfig.json" }, { "path": "./src/plugins/index_pattern_management/tsconfig.json" }, + { "path": "./src/plugins/index_pattern_field_editor/tsconfig.json" }, ] } diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.tsx b/x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.tsx index f6815a4264a5d4..023b620522282b 100644 --- a/x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.tsx +++ b/x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.tsx @@ -8,7 +8,6 @@ import React, { useEffect, useState, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCode } from '@elastic/eui'; import { PainlessLang, PainlessContext } from '@kbn/monaco'; import { EuiFlexGroup, @@ -19,6 +18,7 @@ import { EuiComboBoxOptionOption, EuiLink, EuiCallOut, + EuiCode, } from '@elastic/eui'; import { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c561339d1a6670..1162a9bf00c70e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2591,15 +2591,15 @@ "indexPatternManagement.actions.deleteButton": "削除", "indexPatternManagement.actions.saveButton": "フィールドを保存", "indexPatternManagement.aliasLabel": "エイリアス", - "indexPatternManagement.color.actions": "アクション", - "indexPatternManagement.color.addColorButton": "色を追加", - "indexPatternManagement.color.backgroundLabel": "背景色", - "indexPatternManagement.color.deleteAria": "削除", - "indexPatternManagement.color.deleteTitle": "色のフォーマットを削除", - "indexPatternManagement.color.exampleLabel": "例", - "indexPatternManagement.color.patternLabel": "パターン(正規表現)", - "indexPatternManagement.color.rangeLabel": "範囲(min:max)", - "indexPatternManagement.color.textColorLabel": "文字の色", + "indexPatternFieldEditor.color.actions": "アクション", + "indexPatternFieldEditor.color.addColorButton": "色を追加", + "indexPatternFieldEditor.color.backgroundLabel": "背景色", + "indexPatternFieldEditor.color.deleteAria": "削除", + "indexPatternFieldEditor.color.deleteTitle": "色のフォーマットを削除", + "indexPatternFieldEditor.color.exampleLabel": "例", + "indexPatternFieldEditor.color.patternLabel": "パターン(正規表現)", + "indexPatternFieldEditor.color.rangeLabel": "範囲(min:max)", + "indexPatternFieldEditor.color.textColorLabel": "文字の色", "indexPatternManagement.createHeader": "スクリプトフィールドを作成", "indexPatternManagement.createIndexPattern.betaLabel": "ベータ", "indexPatternManagement.createIndexPattern.description": "インデックスパターンは、{single}または{multiple}データソース、{star}と一致します。", @@ -2663,10 +2663,10 @@ "indexPatternManagement.createIndexPatternHeader": "{indexPatternName}の作成", "indexPatternManagement.customLabel": "カスタムラベル", "indexPatternManagement.dataStreamLabel": "データストリーム", - "indexPatternManagement.date.documentationLabel": "ドキュメント", - "indexPatternManagement.date.momentLabel": "Moment.jsのフォーマットパターン(デフォルト: {defaultPattern})", - "indexPatternManagement.defaultErrorMessage": "このフォーマット構成の使用を試みた際にエラーが発生しました: {message}", - "indexPatternManagement.defaultFormatDropDown": "- デフォルト -", + "indexPatternFieldEditor.date.documentationLabel": "ドキュメント", + "indexPatternFieldEditor.date.momentLabel": "Moment.jsのフォーマットパターン(デフォルト: {defaultPattern})", + "indexPatternFieldEditor.defaultErrorMessage": "このフォーマット構成の使用を試みた際にエラーが発生しました: {message}", + "indexPatternFieldEditor.defaultFormatDropDown": "- デフォルト -", "indexPatternManagement.defaultFormatHeader": "フォーマット (デフォルト: {defaultFormat})", "indexPatternManagement.deleteField.cancelButton": "キャンセル", "indexPatternManagement.deleteField.deleteButton": "削除", @@ -2676,11 +2676,11 @@ "indexPatternManagement.deleteFieldLabel": "削除されたフィールドは復元できません。{separator}続行してよろしいですか?", "indexPatternManagement.disabledCallOutHeader": "スクリプティングが無効です", "indexPatternManagement.disabledCallOutLabel": "Elasticsearchでのすべてのインラインスクリプティングが無効になっています。Kibanaでスクリプトフィールドを使用するには、インラインスクリプティングを有効にする必要があります。", - "indexPatternManagement.duration.decimalPlacesLabel": "小数部分の桁数", - "indexPatternManagement.duration.inputFormatLabel": "インプット形式", - "indexPatternManagement.duration.outputFormatLabel": "アウトプット形式", - "indexPatternManagement.duration.showSuffixLabel": "接尾辞を表示", - "indexPatternManagement.durationErrorMessage": "小数部分の桁数は0から20までの間で指定する必要があります", + "indexPatternFieldEditor.duration.decimalPlacesLabel": "小数部分の桁数", + "indexPatternFieldEditor.duration.inputFormatLabel": "インプット形式", + "indexPatternFieldEditor.duration.outputFormatLabel": "アウトプット形式", + "indexPatternFieldEditor.duration.showSuffixLabel": "接尾辞を表示", + "indexPatternFieldEditor.durationErrorMessage": "小数部分の桁数は0から20までの間で指定する必要があります", "indexPatternManagement.editHeader": "{fieldName}を編集", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全アグリゲーションを実行", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", @@ -2765,7 +2765,6 @@ "indexPatternManagement.editIndexPattern.tabs.sourceHeader": "フィールドフィルター", "indexPatternManagement.editIndexPattern.timeFilterHeader": "時刻フィールド:「{timeFieldName}」", "indexPatternManagement.editIndexPattern.timeFilterLabel.mappingAPILink": "マッピングAPI", - "indexPatternManagement.editIndexPattern.timeFilterLabel.timeFilterDetail": "このページは{indexPatternTitle}インデックス内のすべてのフィールドと、Elasticsearchに記録された各フィールドのコアタイプを一覧表示します。フィールドタイプを変更するにはElasticsearchを使用します", "indexPatternManagement.editIndexPatternLiveRegionAriaLabel": "インデックスパターン", "indexPatternManagement.emptyIndexPatternPrompt.documentation": "ドキュメンテーションを表示", "indexPatternManagement.emptyIndexPatternPrompt.indexPatternExplanation": "Kibanaでは、検索するインデックスを特定するためにインデックスパターンが必要です。インデックスパターンは、昨日のログデータなど特定のインデックス、またはログデータを含むすべてのインデックスを参照できます。", @@ -2774,7 +2773,6 @@ "indexPatternManagement.emptyIndexPatternPrompt.youHaveData": "Elasticsearchにデータがあります。", "indexPatternManagement.fieldTypeConflict": "フィールドタイプの矛盾", "indexPatternManagement.formatHeader": "フォーマット", - "indexPatternManagement.formatLabel": "フォーマットは、特定の値の表示形式を管理できます。また、値を完全に変更したり、ディスカバリでのハイライト機能を無効にしたりすることも可能です。", "indexPatternManagement.frozenLabel": "凍結", "indexPatternManagement.indexLabel": "インデックス", "indexPatternManagement.indexNameLabel": "インデックス名", @@ -2791,19 +2789,6 @@ "indexPatternManagement.indexPatternTable.indexPatternExplanation": "Elasticsearchからのデータの取得に役立つインデックスパターンを作成して管理します。", "indexPatternManagement.indexPatternTable.title": "インデックスパターン", "indexPatternManagement.labelHelpText": "このフィールドが Discover、Maps、Visualize に表示されるときに使用するカスタムラベルを設定します。現在、クエリとフィルターはカスタムラベルをサポートせず、元のフィールド名が使用されます。", - "indexPatternManagement.labelTemplate.example.idLabel": "ユーザー#{value}", - "indexPatternManagement.labelTemplate.example.output.idLabel": "ユーザー", - "indexPatternManagement.labelTemplate.example.output.pathLabel": "アセットを表示", - "indexPatternManagement.labelTemplate.example.pathLabel": "アセットを表示", - "indexPatternManagement.labelTemplate.examplesHeader": "例", - "indexPatternManagement.labelTemplate.inputHeader": "インプット", - "indexPatternManagement.labelTemplate.labelHeader": "ラベルテンプレート", - "indexPatternManagement.labelTemplate.outputHeader": "アウトプット", - "indexPatternManagement.labelTemplate.urlHeader": "URLテンプレート", - "indexPatternManagement.labelTemplate.urlLabel": "フォーマット済みURL", - "indexPatternManagement.labelTemplate.valueLabel": "フィールド値", - "indexPatternManagement.labelTemplateHeader": "ラベルテンプレート", - "indexPatternManagement.labelTemplateLabel": "このフィールドのURLが長い場合、URLのテキストバージョン用の代替テンプレートを使用すると良いかもしれません。URLの代わりに表示されますが、URLにリンクされます。このフォーマットは、値の投入に二重中括弧の表記{doubleCurlyBraces}を使用する文字列です。次の値にアクセスできます。", "indexPatternManagement.languageLabel": "言語", "indexPatternManagement.mappingConflictLabel.mappingConflictDetail": "{mappingConflict} {fieldName}というフィールドはすでに存在します。スクリプトフィールドに同じ名前を付けると、同時に両方のフィールドにクエリが実行できなくなります。", "indexPatternManagement.mappingConflictLabel.mappingConflictLabel": "マッピングの矛盾:", @@ -2811,27 +2796,27 @@ "indexPatternManagement.nameErrorMessage": "名前が必要です", "indexPatternManagement.nameLabel": "名前", "indexPatternManagement.namePlaceholder": "新規スクリプトフィールド", - "indexPatternManagement.number.documentationLabel": "ドキュメント", - "indexPatternManagement.number.numeralLabel": "Numeral.js のフォーマットパターン (デフォルト: {defaultPattern})", + "indexPatternFieldEditor.number.documentationLabel": "ドキュメント", + "indexPatternFieldEditor.number.numeralLabel": "Numeral.js のフォーマットパターン (デフォルト: {defaultPattern})", "indexPatternManagement.popularityLabel": "利用頻度", - "indexPatternManagement.samples.inputHeader": "インプット", - "indexPatternManagement.samples.outputHeader": "アウトプット", - "indexPatternManagement.samplesHeader": "サンプル", + "indexPatternFieldEditor.samples.inputHeader": "インプット", + "indexPatternFieldEditor.samples.outputHeader": "アウトプット", + "indexPatternFieldEditor.samplesHeader": "サンプル", "indexPatternManagement.script.accessWithLabel": "{code} でフィールドにアクセスします。", "indexPatternManagement.script.getHelpLabel": "構文のヒントを得たり、スクリプトの結果をプレビューしたりできます。", "indexPatternManagement.scriptingLanguages.errorFetchingToastDescription": "Elasticsearchから利用可能なスクリプト言語の取得中にエラーが発生しました", "indexPatternManagement.scriptInvalidErrorMessage": "スクリプトが無効です。詳細については、スクリプトプレビューを表示してください", "indexPatternManagement.scriptLabel": "スクリプト", "indexPatternManagement.scriptRequiredErrorMessage": "スクリプトが必要です", - "indexPatternManagement.staticLookup.actions": "アクション", - "indexPatternManagement.staticLookup.addEntryButton": "エントリーを追加", - "indexPatternManagement.staticLookup.deleteAria": "削除", - "indexPatternManagement.staticLookup.deleteTitle": "エントリーの削除", - "indexPatternManagement.staticLookup.keyLabel": "キー", - "indexPatternManagement.staticLookup.leaveBlankPlaceholder": "値をそのままにするには空欄にします", - "indexPatternManagement.staticLookup.unknownKeyLabel": "不明なキーの値", - "indexPatternManagement.staticLookup.valueLabel": "値", - "indexPatternManagement.string.transformLabel": "変換", + "indexPatternFieldEditor.staticLookup.actions": "アクション", + "indexPatternFieldEditor.staticLookup.addEntryButton": "エントリーを追加", + "indexPatternFieldEditor.staticLookup.deleteAria": "削除", + "indexPatternFieldEditor.staticLookup.deleteTitle": "エントリーの削除", + "indexPatternFieldEditor.staticLookup.keyLabel": "キー", + "indexPatternFieldEditor.staticLookup.leaveBlankPlaceholder": "値をそのままにするには空欄にします", + "indexPatternFieldEditor.staticLookup.unknownKeyLabel": "不明なキーの値", + "indexPatternFieldEditor.staticLookup.valueLabel": "値", + "indexPatternFieldEditor.string.transformLabel": "変換", "indexPatternManagement.syntax.default.formatLabel": "doc['some_field'].value", "indexPatternManagement.syntax.defaultLabel.defaultDetail": "デフォルトで、KibanaのスクリプトフィールドはElasticsearchでの使用を目的に特別に開発されたシンプルでセキュアなスクリプト言語の{painless}を使用します。ドキュメントの値にアクセスするには次のフォーマットを使用します。", "indexPatternManagement.syntax.defaultLabel.painlessLink": "Painless", @@ -2862,27 +2847,18 @@ "indexPatternManagement.testScript.resultsLabel": "最初の10件", "indexPatternManagement.testScript.resultsTitle": "結果を表示", "indexPatternManagement.testScript.submitButtonLabel": "スクリプトを実行", - "indexPatternManagement.truncate.lengthLabel": "フィールドの長さ", + "indexPatternFieldEditor.truncate.lengthLabel": "フィールドの長さ", "indexPatternManagement.typeLabel": "型", - "indexPatternManagement.url.heightLabel": "高さ", - "indexPatternManagement.url.labelTemplateHelpText": "ラベルテンプレートのヘルプ", - "indexPatternManagement.url.labelTemplateLabel": "ラベルテンプレート", - "indexPatternManagement.url.offLabel": "オフ", - "indexPatternManagement.url.onLabel": "オン", - "indexPatternManagement.url.openTabLabel": "新規タブで開く", - "indexPatternManagement.url.template.helpLinkText": "URLテンプレートのヘルプ", - "indexPatternManagement.url.typeLabel": "型", - "indexPatternManagement.url.urlTemplateLabel": "URLテンプレート", - "indexPatternManagement.url.widthLabel": "幅", - "indexPatternManagement.urlTemplate.examplesHeader": "例", - "indexPatternManagement.urlTemplate.inputHeader": "インプット", - "indexPatternManagement.urlTemplate.outputHeader": "アウトプット", - "indexPatternManagement.urlTemplate.rawValueLabel": "非エスケープ値", - "indexPatternManagement.urlTemplate.templateHeader": "テンプレート", - "indexPatternManagement.urlTemplate.valueLabel": "URLエスケープ値", - "indexPatternManagement.urlTemplateHeader": "URLテンプレート", - "indexPatternManagement.urlTemplateLabel.fieldDetail": "フィールドにURLの一部のみが含まれている場合、{strongUrlTemplate}でその値を完全なURLとしてフォーマットできます。このフォーマットは、値の投入に二重中括弧の表記{doubleCurlyBraces}を使用する文字列です。次の値にアクセスできます。", - "indexPatternManagement.urlTemplateLabel.strongUrlTemplateLabel": "URLテンプレート", + "indexPatternFieldEditor.url.heightLabel": "高さ", + "indexPatternFieldEditor.url.labelTemplateHelpText": "ラベルテンプレートのヘルプ", + "indexPatternFieldEditor.url.labelTemplateLabel": "ラベルテンプレート", + "indexPatternFieldEditor.url.offLabel": "オフ", + "indexPatternFieldEditor.url.onLabel": "オン", + "indexPatternFieldEditor.url.openTabLabel": "新規タブで開く", + "indexPatternFieldEditor.url.template.helpLinkText": "URLテンプレートのヘルプ", + "indexPatternFieldEditor.url.typeLabel": "型", + "indexPatternFieldEditor.url.urlTemplateLabel": "URLテンプレート", + "indexPatternFieldEditor.url.widthLabel": "幅", "indexPatternManagement.warningCallOut.descriptionLabel": "計算値の表示と集約にスクリプトフィールドが使用できます。そのため非常に遅い場合があり、適切に行わないとKibanaが使用できなくなる可能性もあります。この場合安全策はありません。入力ミスがあると、あちこちに予期せぬ例外が起こります!", "indexPatternManagement.warningCallOutHeader": "十分ご注意ください", "indexPatternManagement.warningCallOutLabel.callOutDetail": "スクリプトフィールドを使う前に、{scripFields}と{scriptsInAggregation}についてよく理解するようにしてください。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ab09f6c1ec56e8..fc658ae8ce719e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2595,15 +2595,15 @@ "indexPatternManagement.actions.deleteButton": "删除", "indexPatternManagement.actions.saveButton": "保存字段", "indexPatternManagement.aliasLabel": "别名", - "indexPatternManagement.color.actions": "操作", - "indexPatternManagement.color.addColorButton": "添加颜色", - "indexPatternManagement.color.backgroundLabel": "背景色", - "indexPatternManagement.color.deleteAria": "删除", - "indexPatternManagement.color.deleteTitle": "删除颜色格式", - "indexPatternManagement.color.exampleLabel": "示例", - "indexPatternManagement.color.patternLabel": "模式(正则表达式)", - "indexPatternManagement.color.rangeLabel": "范围(最小值:最大值)", - "indexPatternManagement.color.textColorLabel": "文本颜色", + "indexPatternFieldEditor.color.actions": "操作", + "indexPatternFieldEditor.color.addColorButton": "添加颜色", + "indexPatternFieldEditor.color.backgroundLabel": "背景色", + "indexPatternFieldEditor.color.deleteAria": "删除", + "indexPatternFieldEditor.color.deleteTitle": "删除颜色格式", + "indexPatternFieldEditor.color.exampleLabel": "示例", + "indexPatternFieldEditor.color.patternLabel": "模式(正则表达式)", + "indexPatternFieldEditor.color.rangeLabel": "范围(最小值:最大值)", + "indexPatternFieldEditor.color.textColorLabel": "文本颜色", "indexPatternManagement.createHeader": "创建脚本字段", "indexPatternManagement.createIndexPattern.betaLabel": "公测版", "indexPatternManagement.createIndexPattern.description": "索引模式可以匹配单个源,例如 {single} 或 {multiple} 个数据源、{star}。", @@ -2667,10 +2667,10 @@ "indexPatternManagement.createIndexPatternHeader": "创建 {indexPatternName}", "indexPatternManagement.customLabel": "定制标签", "indexPatternManagement.dataStreamLabel": "数据流", - "indexPatternManagement.date.documentationLabel": "文档", - "indexPatternManagement.date.momentLabel": "Moment.js 格式模式(默认值:{defaultPattern})", - "indexPatternManagement.defaultErrorMessage": "尝试使用此格式配置时发生错误:{message}", - "indexPatternManagement.defaultFormatDropDown": "- 默认值 -", + "indexPatternFieldEditor.date.documentationLabel": "文档", + "indexPatternFieldEditor.date.momentLabel": "Moment.js 格式模式(默认值:{defaultPattern})", + "indexPatternFieldEditor.defaultErrorMessage": "尝试使用此格式配置时发生错误:{message}", + "indexPatternFieldEditor.defaultFormatDropDown": "- 默认值 -", "indexPatternManagement.defaultFormatHeader": "格式(默认值:{defaultFormat})", "indexPatternManagement.deleteField.cancelButton": "取消", "indexPatternManagement.deleteField.deleteButton": "删除", @@ -2680,11 +2680,11 @@ "indexPatternManagement.deleteFieldLabel": "您无法恢复已删除字段。{separator}确定要执行此操作?", "indexPatternManagement.disabledCallOutHeader": "脚本已禁用", "indexPatternManagement.disabledCallOutLabel": "所有内联脚本在 Elasticsearch 中已禁用。必须至少为一种语言启用内联脚本,才能在 Kibana 中使用脚本字段。", - "indexPatternManagement.duration.decimalPlacesLabel": "小数位数", - "indexPatternManagement.duration.inputFormatLabel": "输入格式", - "indexPatternManagement.duration.outputFormatLabel": "输出格式", - "indexPatternManagement.duration.showSuffixLabel": "显示后缀", - "indexPatternManagement.durationErrorMessage": "小数位数必须介于 0 和 20 之间", + "indexPatternFieldEditor.duration.decimalPlacesLabel": "小数位数", + "indexPatternFieldEditor.duration.inputFormatLabel": "输入格式", + "indexPatternFieldEditor.duration.outputFormatLabel": "输出格式", + "indexPatternFieldEditor.duration.showSuffixLabel": "显示后缀", + "indexPatternFieldEditor.durationErrorMessage": "小数位数必须介于 0 和 20 之间", "indexPatternManagement.editHeader": "编辑 {fieldName}", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", @@ -2769,7 +2769,6 @@ "indexPatternManagement.editIndexPattern.tabs.sourceHeader": "字段筛选", "indexPatternManagement.editIndexPattern.timeFilterHeader": "时间字段:“{timeFieldName}”", "indexPatternManagement.editIndexPattern.timeFilterLabel.mappingAPILink": "映射 API", - "indexPatternManagement.editIndexPattern.timeFilterLabel.timeFilterDetail": "此页根据 Elasticsearch 的记录列出“{indexPatternTitle}”索引中的每个字段以及字段的关联核心类型。要更改字段类型,请使用 Elasticsearch", "indexPatternManagement.editIndexPatternLiveRegionAriaLabel": "索引模式", "indexPatternManagement.emptyIndexPatternPrompt.documentation": "阅读文档", "indexPatternManagement.emptyIndexPatternPrompt.indexPatternExplanation": "Kibana 需要索引模式,以识别您要浏览的索引。索引模式可以指向特定索引(例如昨天的日志数据),或包含日志数据的所有索引。", @@ -2778,7 +2777,6 @@ "indexPatternManagement.emptyIndexPatternPrompt.youHaveData": "您在 Elasticsearch 中有数据。", "indexPatternManagement.fieldTypeConflict": "字段类型冲突", "indexPatternManagement.formatHeader": "格式", - "indexPatternManagement.formatLabel": "设置格式允许您控制特定值的显示方式。其还会导致值完全更改,并阻止 Discover 中的突出显示起作用。", "indexPatternManagement.frozenLabel": "已冻结", "indexPatternManagement.indexLabel": "索引", "indexPatternManagement.indexNameLabel": "索引名称", @@ -2795,19 +2793,6 @@ "indexPatternManagement.indexPatternTable.indexPatternExplanation": "创建和管理帮助您从 Elasticsearch 中检索数据的索引模式。", "indexPatternManagement.indexPatternTable.title": "索引模式", "indexPatternManagement.labelHelpText": "设置此字段在 Discover、Maps 和 Visualize 中显示时要使用的定制标签。当前查询和筛选不支持定制标签,将使用原始字段名称。", - "indexPatternManagement.labelTemplate.example.idLabel": "用户 #{value}", - "indexPatternManagement.labelTemplate.example.output.idLabel": "用户", - "indexPatternManagement.labelTemplate.example.output.pathLabel": "查看资产", - "indexPatternManagement.labelTemplate.example.pathLabel": "查看资产", - "indexPatternManagement.labelTemplate.examplesHeader": "示例", - "indexPatternManagement.labelTemplate.inputHeader": "输入", - "indexPatternManagement.labelTemplate.labelHeader": "标签模板", - "indexPatternManagement.labelTemplate.outputHeader": "输出", - "indexPatternManagement.labelTemplate.urlHeader": "URL 模板", - "indexPatternManagement.labelTemplate.urlLabel": "格式化 URL", - "indexPatternManagement.labelTemplate.valueLabel": "字段值", - "indexPatternManagement.labelTemplateHeader": "标签模板", - "indexPatternManagement.labelTemplateLabel": "如果此字段中的 URL 很长,为 URL 的文本版本提供备选模板可能会很有用。该文本将会显示,而非显示该 url,但仍会链接到该 URL。该格式是使用双大括号表示法 {doubleCurlyBraces} 来注入值的字符串。可以访问以下值:", "indexPatternManagement.languageLabel": "语言", "indexPatternManagement.mappingConflictLabel.mappingConflictDetail": "{mappingConflict}您已经有名称为 {fieldName} 的字段。使用相同的名称命名您的脚本字段意味着您将无法同时查找两个字段。", "indexPatternManagement.mappingConflictLabel.mappingConflictLabel": "映射冲突:", @@ -2815,27 +2800,27 @@ "indexPatternManagement.nameErrorMessage": "“名称”必填", "indexPatternManagement.nameLabel": "名称", "indexPatternManagement.namePlaceholder": "新建脚本字段", - "indexPatternManagement.number.documentationLabel": "文档", - "indexPatternManagement.number.numeralLabel": "Numeral.js 格式模式(默认值:{defaultPattern})", + "indexPatternFieldEditor.number.documentationLabel": "文档", + "indexPatternFieldEditor.number.numeralLabel": "Numeral.js 格式模式(默认值:{defaultPattern})", "indexPatternManagement.popularityLabel": "常见度", - "indexPatternManagement.samples.inputHeader": "输入", - "indexPatternManagement.samples.outputHeader": "输出", - "indexPatternManagement.samplesHeader": "样例", + "indexPatternFieldEditor.samples.inputHeader": "输入", + "indexPatternFieldEditor.samples.outputHeader": "输出", + "indexPatternFieldEditor.samplesHeader": "样例", "indexPatternManagement.script.accessWithLabel": "使用 {code} 访问字段。", "indexPatternManagement.script.getHelpLabel": "获取该语法的帮助,预览脚本的结果。", "indexPatternManagement.scriptingLanguages.errorFetchingToastDescription": "从 Elasticsearch 获取可用的脚本语言时出错", "indexPatternManagement.scriptInvalidErrorMessage": "脚本无效。查看脚本预览以了解详情", "indexPatternManagement.scriptLabel": "脚本", "indexPatternManagement.scriptRequiredErrorMessage": "“脚本”必填", - "indexPatternManagement.staticLookup.actions": "操作", - "indexPatternManagement.staticLookup.addEntryButton": "添加条目", - "indexPatternManagement.staticLookup.deleteAria": "删除", - "indexPatternManagement.staticLookup.deleteTitle": "删除条目", - "indexPatternManagement.staticLookup.keyLabel": "键", - "indexPatternManagement.staticLookup.leaveBlankPlaceholder": "留空可使值保持原样", - "indexPatternManagement.staticLookup.unknownKeyLabel": "未知键的值", - "indexPatternManagement.staticLookup.valueLabel": "值", - "indexPatternManagement.string.transformLabel": "转换", + "indexPatternFieldEditor.staticLookup.actions": "操作", + "indexPatternFieldEditor.staticLookup.addEntryButton": "添加条目", + "indexPatternFieldEditor.staticLookup.deleteAria": "删除", + "indexPatternFieldEditor.staticLookup.deleteTitle": "删除条目", + "indexPatternFieldEditor.staticLookup.keyLabel": "键", + "indexPatternFieldEditor.staticLookup.leaveBlankPlaceholder": "留空可使值保持原样", + "indexPatternFieldEditor.staticLookup.unknownKeyLabel": "未知键的值", + "indexPatternFieldEditor.staticLookup.valueLabel": "值", + "indexPatternFieldEditor.string.transformLabel": "转换", "indexPatternManagement.syntax.default.formatLabel": "doc['some_field'].value", "indexPatternManagement.syntax.defaultLabel.defaultDetail": "默认情况下,Kibana 脚本字段使用 {painless}(一种简单且安全的脚本语言,专用于 Elasticsearch)通过以下格式访问文档中的值:", "indexPatternManagement.syntax.defaultLabel.painlessLink": "Painless", @@ -2866,27 +2851,18 @@ "indexPatternManagement.testScript.resultsLabel": "前 10 个结果", "indexPatternManagement.testScript.resultsTitle": "预览结果", "indexPatternManagement.testScript.submitButtonLabel": "运行脚本", - "indexPatternManagement.truncate.lengthLabel": "字段长度", + "indexPatternFieldEditor.truncate.lengthLabel": "字段长度", "indexPatternManagement.typeLabel": "类型", - "indexPatternManagement.url.heightLabel": "高", - "indexPatternManagement.url.labelTemplateHelpText": "标签模板帮助", - "indexPatternManagement.url.labelTemplateLabel": "标签模板", - "indexPatternManagement.url.offLabel": "关闭", - "indexPatternManagement.url.onLabel": "开启", - "indexPatternManagement.url.openTabLabel": "在新选项卡中打开", - "indexPatternManagement.url.template.helpLinkText": "URL 模板帮助", - "indexPatternManagement.url.typeLabel": "类型", - "indexPatternManagement.url.urlTemplateLabel": "URL 模板", - "indexPatternManagement.url.widthLabel": "宽", - "indexPatternManagement.urlTemplate.examplesHeader": "示例", - "indexPatternManagement.urlTemplate.inputHeader": "输入", - "indexPatternManagement.urlTemplate.outputHeader": "输出", - "indexPatternManagement.urlTemplate.rawValueLabel": "非转义值", - "indexPatternManagement.urlTemplate.templateHeader": "模板", - "indexPatternManagement.urlTemplate.valueLabel": "URI 转义值", - "indexPatternManagement.urlTemplateHeader": "Url 模板", - "indexPatternManagement.urlTemplateLabel.fieldDetail": "如果字段仅包含 URL 的一部分,则 {strongUrlTemplate} 可用于将该值格式化为完整的 URL。该格式是使用双大括号表示法 {doubleCurlyBraces} 来注入值的字符串。可以访问以下值:", - "indexPatternManagement.urlTemplateLabel.strongUrlTemplateLabel": "Url 模板", + "indexPatternFieldEditor.url.heightLabel": "高", + "indexPatternFieldEditor.url.labelTemplateHelpText": "标签模板帮助", + "indexPatternFieldEditor.url.labelTemplateLabel": "标签模板", + "indexPatternFieldEditor.url.offLabel": "关闭", + "indexPatternFieldEditor.url.onLabel": "开启", + "indexPatternFieldEditor.url.openTabLabel": "在新选项卡中打开", + "indexPatternFieldEditor.url.template.helpLinkText": "URL 模板帮助", + "indexPatternFieldEditor.url.typeLabel": "类型", + "indexPatternFieldEditor.url.urlTemplateLabel": "URL 模板", + "indexPatternFieldEditor.url.widthLabel": "宽", "indexPatternManagement.warningCallOut.descriptionLabel": "脚本字段可用于显示并聚合计算值。因此,它们会很慢,如果操作不当,会导致 Kibana 不可用。此处没有安全网。如果拼写错误,则在任何地方都会引发异常!", "indexPatternManagement.warningCallOutHeader": "谨慎操作", "indexPatternManagement.warningCallOutLabel.callOutDetail": "请先熟悉{scripFields}以及{scriptsInAggregation},然后再使用脚本字段。", diff --git a/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js b/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js index 0cad97e268dda0..4fd7c2cc2f0679 100644 --- a/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js +++ b/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js @@ -99,7 +99,7 @@ export default function ({ getService, getPageObjects }) { // ensure all fields are available await PageObjects.settings.clickIndexPatternByName(rollupIndexPatternName); const fields = await PageObjects.settings.getFieldNames(); - expect(fields).to.eql(['_source', '_id', '_type', '_index', '_score', '@timestamp']); + expect(fields).to.eql(['@timestamp', '_id', '_index', '_score', '_source', '_type']); }); after(async () => { From 96bc9e868de56e09c0a03e9a6d40cb2e4905b0ed Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Thu, 18 Feb 2021 13:42:40 -0500 Subject: [PATCH 54/93] [CI] Ping assignees on Github PR comments (#91871) --- vars/githubPr.groovy | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy index eead00c082ba75..d024eb7346f8f7 100644 --- a/vars/githubPr.groovy +++ b/vars/githubPr.groovy @@ -235,6 +235,13 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { messages << "To update your PR or re-run it, just comment with:\n`@elasticmachine merge upstream`" + catchErrors { + def assignees = getAssignees() + if (assignees) { + messages << "cc " + assignees.collect { "@${it}"}.join(" ") + } + } + info.builds << [ status: status, url: env.BUILD_URL, @@ -329,3 +336,19 @@ def shouldCheckCiMetricSuccess() { return true } + +def getPR() { + withGithubCredentials { + def path = "repos/elastic/kibana/pulls/${env.ghprbPullId}" + return githubApi.get(path) + } +} + +def getAssignees() { + def pr = getPR() + if (!pr) { + return [] + } + + return pr.assignees.collect { it.login } +} From 0a685dbb63ea77b82e9eb8a7e55c7a92f3ebf748 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Thu, 18 Feb 2021 13:49:26 -0500 Subject: [PATCH 55/93] [Time to Visualize] Dashboard Save As New by Default (#91761) * changed dashboard save as to have save as new switch on by default --- .../top_nav/__snapshots__/save_modal.test.js.snap | 1 + .../dashboard/public/application/top_nav/save_modal.tsx | 1 + test/functional/page_objects/dashboard_page.ts | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plugins/dashboard/public/application/top_nav/__snapshots__/save_modal.test.js.snap b/src/plugins/dashboard/public/application/top_nav/__snapshots__/save_modal.test.js.snap index bc4ed477d9eea0..f8ba1b38685274 100644 --- a/src/plugins/dashboard/public/application/top_nav/__snapshots__/save_modal.test.js.snap +++ b/src/plugins/dashboard/public/application/top_nav/__snapshots__/save_modal.test.js.snap @@ -2,6 +2,7 @@ exports[`renders DashboardSaveModal 1`] = ` { onClose={this.props.onClose} title={this.props.title} showCopyOnSave={this.props.showCopyOnSave} + initialCopyOnSave={this.props.showCopyOnSave} objectType="dashboard" options={this.renderDashboardSaveOptions()} showDescription={false} diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 9c571f0f0ef86b..465deed4d9039b 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -425,8 +425,9 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide await this.setStoreTimeWithDashboard(saveOptions.storeTimeWithDashboard); } - if (saveOptions.saveAsNew !== undefined) { - await this.setSaveAsNewCheckBox(saveOptions.saveAsNew); + const saveAsNewCheckboxExists = await testSubjects.exists('saveAsNewCheckbox'); + if (saveAsNewCheckboxExists) { + await this.setSaveAsNewCheckBox(Boolean(saveOptions.saveAsNew)); } if (saveOptions.tags) { From 4ce0b6c14fbbbf6e51ff5c9f997edc80f9f0b15b Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Thu, 18 Feb 2021 13:03:50 -0600 Subject: [PATCH 56/93] [DOCS] Adds and updates Visualization advanced settings (#91904) --- docs/management/advanced-options.asciidoc | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index dc0405b22942f6..c7d5242da69de4 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -457,7 +457,7 @@ of buckets to try to represent. [horizontal] [[visualization-visualize-chartslibrary]]`visualization:visualize:legacyChartsLibrary`:: -Enables legacy charts library for area, line and bar charts in visualize. +Enables the legacy charts library for aggregation-based area, line, and bar charts in *Visualize*. [[visualization-colormapping]]`visualization:colorMapping`:: **This setting is deprecated and will not be supported as of 8.0.** @@ -465,24 +465,25 @@ Maps values to specific colors in *Visualize* charts and *TSVB*. This setting do [[visualization-dimmingopacity]]`visualization:dimmingOpacity`:: The opacity of the chart items that are dimmed when highlighting another element -of the chart. The lower this number, the more the highlighted element stands out. -This must be a number between 0 and 1. +of the chart. Use numbers between 0 and 1. The lower the number, the more the highlighted element stands out. + +[[visualization-heatmap-maxbuckets]]`visualization:heatmap:maxBuckets`:: +The maximum number of buckets a datasource can return. High numbers can have a negative impact on your browser rendering performance. [[visualization-regionmap-showwarnings]]`visualization:regionmap:showWarnings`:: Shows a warning in a region map when terms cannot be joined to a shape. [[visualization-tilemap-wmsdefaults]]`visualization:tileMap:WMSdefaults`:: -The default properties for the WMS map server support in the coordinate map. +The default properties for the WMS map server supported in the coordinate map. [[visualization-tilemap-maxprecision]]`visualization:tileMap:maxPrecision`:: -The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high, -and 12 is the maximum. See this -{ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[explanation of cell dimensions]. +The maximum geoHash precision displayed in tile maps. 7 is high, 10 is very high, +and 12 is the maximum. For more information, refer to +{ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[Cell dimensions at the equator]. [[visualize-enablelabs]]`visualize:enableLabs`:: -Enables users to create, view, and edit experimental visualizations. If disabled, -only visualizations that are considered production-ready are available to the -user. +Enables users to create, view, and edit experimental visualizations. When disabled, +only production-ready visualizations are available to users. [float] From 043848787d43ef2ae8efba77c899144291f2d14e Mon Sep 17 00:00:00 2001 From: Brandon Morelli Date: Thu, 18 Feb 2021 11:06:13 -0800 Subject: [PATCH 57/93] docs: add PHP agent info to docs (#91773) --- docs/apm/agent-configuration.asciidoc | 1 + docs/apm/filters.asciidoc | 3 ++- docs/apm/service-maps.asciidoc | 15 ++++++++------- docs/apm/troubleshooting.asciidoc | 1 + 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/apm/agent-configuration.asciidoc b/docs/apm/agent-configuration.asciidoc index d911c2154ea4c5..aaaca867a5a01b 100644 --- a/docs/apm/agent-configuration.asciidoc +++ b/docs/apm/agent-configuration.asciidoc @@ -46,6 +46,7 @@ Go Agent:: {apm-go-ref}/configuration.html[Configuration reference] Java Agent:: {apm-java-ref}/configuration.html[Configuration reference] .NET Agent:: {apm-dotnet-ref}/configuration.html[Configuration reference] Node.js Agent:: {apm-node-ref}/configuration.html[Configuration reference] +PHP Agent:: _Not yet supported_ Python Agent:: {apm-py-ref}/configuration.html[Configuration reference] Ruby Agent:: {apm-ruby-ref}/configuration.html[Configuration reference] Real User Monitoring (RUM) Agent:: {apm-rum-ref}/configuration.html[Configuration reference] diff --git a/docs/apm/filters.asciidoc b/docs/apm/filters.asciidoc index c405ea10ade3d2..3fe9146658eefc 100644 --- a/docs/apm/filters.asciidoc +++ b/docs/apm/filters.asciidoc @@ -52,8 +52,9 @@ See the documentation for each agent you're using to learn how to configure serv * *Go:* {apm-go-ref}/configuration.html#config-environment[`ELASTIC_APM_ENVIRONMENT`] * *Java:* {apm-java-ref}/config-core.html#config-environment[`environment`] -* *.NET* {apm-dotnet-ref}/config-core.html#config-environment[`Environment`] +* *.NET:* {apm-dotnet-ref}/config-core.html#config-environment[`Environment`] * *Node.js:* {apm-node-ref}/configuration.html#environment[`environment`] +* *PHP:* {apm-php-ref}/configuration-reference.html#config-environment[`environment`] * *Python:* {apm-py-ref}/configuration.html#config-environment[`environment`] * *Ruby:* {apm-ruby-ref}/configuration.html#config-environment[`environment`] * *Real User Monitoring:* {apm-rum-ref}/configuration.html#environment[`environment`] diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc index 7cc4da8a1fc1de..a3ac62a4c83436 100644 --- a/docs/apm/service-maps.asciidoc +++ b/docs/apm/service-maps.asciidoc @@ -87,10 +87,11 @@ Type and subtype are based on `span.type`, and `span.subtype`. Service maps are supported for the following Agent versions: [horizontal] -Go Agent:: ≥ v1.7.0 -Java Agent:: ≥ v1.13.0 -.NET Agent:: ≥ v1.3.0 -Node.js Agent:: ≥ v3.6.0 -Python Agent:: ≥ v5.5.0 -Ruby Agent:: ≥ v3.6.0 -Real User Monitoring (RUM) Agent:: ≥ v4.7.0 +Go agent:: ≥ v1.7.0 +Java agent:: ≥ v1.13.0 +.NET agent:: ≥ v1.3.0 +Node.js agent:: ≥ v3.6.0 +PHP agent:: _Not yet supported_ +Python agent:: ≥ v5.5.0 +Ruby agent:: ≥ v3.6.0 +Real User Monitoring (RUM) agent:: ≥ v4.7.0 diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc index 465a3d652046db..5049321363f88e 100644 --- a/docs/apm/troubleshooting.asciidoc +++ b/docs/apm/troubleshooting.asciidoc @@ -17,6 +17,7 @@ don't forget to check our other troubleshooting guides or discussion forum: * {apm-go-ref}/troubleshooting.html[Go agent troubleshooting] * {apm-java-ref}/trouble-shooting.html[Java agent troubleshooting] * {apm-node-ref}/troubleshooting.html[Node.js agent troubleshooting] +* {apm-php-ref}/troubleshooting.html[PHP agent troubleshooting] * {apm-py-ref}/troubleshooting.html[Python agent troubleshooting] * {apm-ruby-ref}/debugging.html[Ruby agent troubleshooting] * {apm-rum-ref/troubleshooting.html[RUM troubleshooting] From 03206b688ab239ce837b469a8a690eab30a0a6ff Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Thu, 18 Feb 2021 14:13:23 -0500 Subject: [PATCH 58/93] [CI] Build and publish storybooks (#87701) --- .ci/.storybook/main.js | 28 +++++++ .eslintignore | 1 + packages/kbn-storybook/lib/default_config.ts | 7 ++ .../kbn-storybook/lib/templates/index.ejs | 10 +-- src/dev/storybook/aliases.ts | 2 + test/scripts/jenkins_storybook.sh | 23 +++++ vars/githubCommitStatus.groovy | 6 +- vars/githubPr.groovy | 9 ++ vars/kibanaPipeline.groovy | 1 + vars/storybooks.groovy | 83 +++++++++++++++++++ vars/tasks.groovy | 6 ++ .../canvas/storybook/preview-head.html | 4 +- 12 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 .ci/.storybook/main.js create mode 100755 test/scripts/jenkins_storybook.sh create mode 100644 vars/storybooks.groovy diff --git a/.ci/.storybook/main.js b/.ci/.storybook/main.js new file mode 100644 index 00000000000000..e399ec087e1687 --- /dev/null +++ b/.ci/.storybook/main.js @@ -0,0 +1,28 @@ +/* + * 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. + */ + +const config = require('@kbn/storybook').defaultConfig; +const aliases = require('../../src/dev/storybook/aliases.ts').storybookAliases; + +config.refs = {}; + +for (const alias of Object.keys(aliases).filter((a) => a !== 'ci_composite')) { + // snake_case -> Title Case + const title = alias + .replace(/_/g, ' ') + .split(' ') + .map((n) => n[0].toUpperCase() + n.slice(1)) + .join(' '); + + config.refs[alias] = { + title: title, + url: `${process.env.STORYBOOK_BASE_URL}/${alias}`, + }; +} + +module.exports = config; diff --git a/.eslintignore b/.eslintignore index ea8ab55ad77269..4559711bb9dd31 100644 --- a/.eslintignore +++ b/.eslintignore @@ -15,6 +15,7 @@ node_modules target snapshots.js +!/.ci !/.eslintrc.js !.storybook diff --git a/packages/kbn-storybook/lib/default_config.ts b/packages/kbn-storybook/lib/default_config.ts index 53c51e9cf29fe6..1b049761a3a985 100644 --- a/packages/kbn-storybook/lib/default_config.ts +++ b/packages/kbn-storybook/lib/default_config.ts @@ -14,4 +14,11 @@ export const defaultConfig: StorybookConfig = { typescript: { reactDocgen: false, }, + webpackFinal: (config, options) => { + if (process.env.CI) { + config.parallelism = 4; + config.cache = true; + } + return config; + }, }; diff --git a/packages/kbn-storybook/lib/templates/index.ejs b/packages/kbn-storybook/lib/templates/index.ejs index a4f8204c95d7a2..b193c87824d40f 100644 --- a/packages/kbn-storybook/lib/templates/index.ejs +++ b/packages/kbn-storybook/lib/templates/index.ejs @@ -16,12 +16,12 @@ - - - - + + + + <% if (typeof headHtmlSnippet !== 'undefined') { %> <%= headHtmlSnippet %> <% } %> <% diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index c72c81f489fb9d..f1a3737747573d 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +// Please also add new aliases to test/scripts/jenkins_storybook.sh export const storybookAliases = { apm: 'x-pack/plugins/apm/.storybook', canvas: 'x-pack/plugins/canvas/storybook', codeeditor: 'src/plugins/kibana_react/public/code_editor/.storybook', + ci_composite: '.ci/.storybook', url_template_editor: 'src/plugins/kibana_react/public/url_template_editor/.storybook', dashboard: 'src/plugins/dashboard/.storybook', dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook', diff --git a/test/scripts/jenkins_storybook.sh b/test/scripts/jenkins_storybook.sh new file mode 100755 index 00000000000000..8ebfc1035fe1f1 --- /dev/null +++ b/test/scripts/jenkins_storybook.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +cd "$XPACK_DIR/plugins/canvas" +node scripts/storybook --dll + +cd "$KIBANA_DIR" + +# yarn storybook --site apm # TODO re-enable after being fixed +yarn storybook --site canvas +yarn storybook --site ci_composite +yarn storybook --site url_template_editor +yarn storybook --site codeeditor +yarn storybook --site dashboard +yarn storybook --site dashboard_enhanced +yarn storybook --site data_enhanced +yarn storybook --site embeddable +yarn storybook --site infra +yarn storybook --site security_solution +yarn storybook --site ui_actions_enhanced +yarn storybook --site observability +yarn storybook --site presentation diff --git a/vars/githubCommitStatus.groovy b/vars/githubCommitStatus.groovy index 248d226169a61e..175dbe0c90542f 100644 --- a/vars/githubCommitStatus.groovy +++ b/vars/githubCommitStatus.groovy @@ -41,13 +41,15 @@ def trackBuild(commit, context, Closure closure) { } // state: error|failure|pending|success -def create(sha, state, description, context) { +def create(sha, state, description, context, targetUrl = null) { + targetUrl = targetUrl ?: env.BUILD_URL + withGithubCredentials { return githubApi.post("repos/elastic/kibana/statuses/${sha}", [ state: state, description: description, context: context, - target_url: env.BUILD_URL + target_url: targetUrl.toString() ]) } } diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy index d024eb7346f8f7..a2a3a81f253a02 100644 --- a/vars/githubPr.groovy +++ b/vars/githubPr.groovy @@ -169,12 +169,18 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { ? getBuildStatusIncludingMetrics() : buildUtils.getBuildStatus() + def storybooksUrl = buildState.get('storybooksUrl') + def storybooksMessage = storybooksUrl ? "* [Storybooks Preview](${storybooksUrl})" : "* Storybooks not built" + if (!isFinal) { + storybooksMessage = storybooksUrl ? storybooksMessage : "* Storybooks not built yet" + def failuresPart = status != 'SUCCESS' ? ', with failures' : '' messages << """ ## :hourglass_flowing_sand: Build in-progress${failuresPart} * [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL}) * Commit: ${getCommitHash()} + ${storybooksMessage} * This comment will update when the build is complete """ } else if (status == 'SUCCESS') { @@ -182,6 +188,7 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { ## :green_heart: Build Succeeded * [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL}) * Commit: ${getCommitHash()} + ${storybooksMessage} ${getDocsChangesLink()} """ } else if(status == 'UNSTABLE') { @@ -189,6 +196,7 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { ## :yellow_heart: Build succeeded, but was flaky * [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL}) * Commit: ${getCommitHash()} + ${storybooksMessage} ${getDocsChangesLink()} """.stripIndent() @@ -204,6 +212,7 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { ## :broken_heart: Build Failed * [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL}) * Commit: ${getCommitHash()} + ${storybooksMessage} * [Pipeline Steps](${env.BUILD_URL}flowGraphTable) (look for red circles / failed steps) * [Interpreting CI Failures](https://www.elastic.co/guide/en/kibana/current/interpreting-ci-failures.html) ${getDocsChangesLink()} diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 7adf755bfc5834..1fe1d78658669a 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -460,6 +460,7 @@ def allCiTasks() { tasks.test() tasks.functionalOss() tasks.functionalXpack() + tasks.storybooksCi() } }, jest: { diff --git a/vars/storybooks.groovy b/vars/storybooks.groovy new file mode 100644 index 00000000000000..f3c4a97a7d4364 --- /dev/null +++ b/vars/storybooks.groovy @@ -0,0 +1,83 @@ +def getStorybooksBucket() { + return "ci-artifacts.kibana.dev/storybooks" +} + +def getDestinationDir() { + return env.ghprbPullId ? "pr-${env.ghprbPullId}" : buildState.get('checkoutInfo').branch.replace("/", "__") +} + +def getUrl() { + return "https://${getStorybooksBucket()}/${getDestinationDir()}" +} + +def getUrlLatest() { + return "${getUrl()}/latest" +} + +def getUrlForCommit() { + return "${getUrl()}/${buildState.get('checkoutInfo').commit}" +} + +def upload() { + dir("built_assets/storybook") { + sh "mv ci_composite composite" + + def storybooks = sh( + script: 'ls -1d */', + returnStdout: true + ).trim() + .split('\n') + .collect { it.replace('/', '') } + .findAll { it != 'composite' } + + def listHtml = storybooks.collect { """
  • ${it}
  • """ }.join("\n") + + def html = """ + + +

    Storybooks

    +

    Composite Storybook

    +

    All

    +
      + ${listHtml} +
    + + + """ + + writeFile(file: 'index.html', text: html) + + withGcpServiceAccount.fromVaultSecret('secret/kibana-issues/dev/ci-artifacts-key', 'value') { + kibanaPipeline.bash(""" + gsutil -q -m cp -r -z js,css,html,json,map,txt,svg '*' 'gs://${getStorybooksBucket()}/${getDestinationDir()}/${buildState.get('checkoutInfo').commit}/' + gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp -z html 'index.html' 'gs://${getStorybooksBucket()}/${getDestinationDir()}/latest/' + """, "Upload Storybooks to GCS") + } + + buildState.set('storybooksUrl', getUrlForCommit()) + } +} + +def build() { + withEnv(["STORYBOOK_BASE_URL=${getUrlForCommit()}"]) { + kibanaPipeline.bash('test/scripts/jenkins_storybook.sh', 'Build Storybooks') + } +} + +def buildAndUpload() { + def sha = buildState.get('checkoutInfo').commit + def context = 'Build and Publish Storybooks' + + githubCommitStatus.create(sha, 'pending', 'Building Storybooks', context) + + try { + build() + upload() + githubCommitStatus.create(sha, 'success', 'Storybooks built', context, getUrlForCommit()) + } catch(ex) { + githubCommitStatus.create(sha, 'error', 'Building Storybooks failed', context) + throw ex + } +} + +return this diff --git a/vars/tasks.groovy b/vars/tasks.groovy index 7c40966ff5e04c..846eed85fb0762 100644 --- a/vars/tasks.groovy +++ b/vars/tasks.groovy @@ -124,4 +124,10 @@ def functionalXpack(Map params = [:]) { } } +def storybooksCi() { + task { + storybooks.buildAndUpload() + } +} + return this diff --git a/x-pack/plugins/canvas/storybook/preview-head.html b/x-pack/plugins/canvas/storybook/preview-head.html index bef08a5120a36b..f8a7de6ddbaf1a 100644 --- a/x-pack/plugins/canvas/storybook/preview-head.html +++ b/x-pack/plugins/canvas/storybook/preview-head.html @@ -2,5 +2,5 @@ This file is looked for by Storybook and included in the HEAD element if it exists. This is how we load the DLL content into the Storybook UI. --> - - + + From a82b13d147d9c4124266eb856bacf8cdb9e85d21 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Thu, 18 Feb 2021 14:52:50 -0500 Subject: [PATCH 59/93] [FTSR] Convert to tasks and add jest/api integration suites (#91770) --- .ci/Jenkinsfile_flaky | 137 +++++++++++++++++++++---------------- vars/kibanaPipeline.groovy | 11 +-- 2 files changed, 85 insertions(+), 63 deletions(-) diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky index b9880c410fc686..7eafc66465bc72 100644 --- a/.ci/Jenkinsfile_flaky +++ b/.ci/Jenkinsfile_flaky @@ -3,47 +3,39 @@ library 'kibana-pipeline-library' kibanaLibrary.load() -def CI_GROUP_PARAM = params.CI_GROUP +def TASK_PARAM = params.TASK ?: params.CI_GROUP // Looks like 'oss:ciGroup:1', 'oss:firefoxSmoke' -def JOB_PARTS = CI_GROUP_PARAM.split(':') +def JOB_PARTS = TASK_PARAM.split(':') def IS_XPACK = JOB_PARTS[0] == 'xpack' -def JOB = JOB_PARTS[1] +def JOB = JOB_PARTS.size() > 1 ? JOB_PARTS[1] : JOB_PARTS[0] def CI_GROUP = JOB_PARTS.size() > 2 ? JOB_PARTS[2] : '' def EXECUTIONS = params.NUMBER_EXECUTIONS.toInteger() def AGENT_COUNT = getAgentCount(EXECUTIONS) - -def worker = getWorkerFromParams(IS_XPACK, JOB, CI_GROUP) - -def workerFailures = [] +def NEED_BUILD = JOB != 'jestIntegration' && JOB != 'apiIntegration' currentBuild.displayName += trunc(" ${params.GITHUB_OWNER}:${params.branch_specifier}", 24) currentBuild.description = "${params.CI_GROUP}
    Agents: ${AGENT_COUNT}
    Executions: ${params.NUMBER_EXECUTIONS}" kibanaPipeline(timeoutMinutes: 180) { def agents = [:] + def workerFailures = [] + + def worker = getWorkerFromParams(IS_XPACK, JOB, CI_GROUP) + for(def agentNumber = 1; agentNumber <= AGENT_COUNT; agentNumber++) { - def agentNumberInside = agentNumber def agentExecutions = floor(EXECUTIONS/AGENT_COUNT) + (agentNumber <= EXECUTIONS%AGENT_COUNT ? 1 : 0) + agents["agent-${agentNumber}"] = { - catchErrors { - print "Agent ${agentNumberInside} - ${agentExecutions} executions" - - withEnv([ - 'IGNORE_SHIP_CI_STATS_ERROR=true', - ]) { - workers.functional('flaky-test-runner', { - if (!IS_XPACK) { - kibanaPipeline.buildOss() - if (CI_GROUP == '1') { - runbld("./test/scripts/jenkins_build_kbn_sample_panel_action.sh", "Build kbn tp sample panel action for ciGroup1") - } - } else { - kibanaPipeline.buildXpack() - } - }, getWorkerMap(agentNumberInside, agentExecutions, worker, workerFailures))() - } - } + agentProcess( + agentNumber: agentNumber, + agentExecutions: agentExecutions, + worker: worker, + workerFailures: workerFailures, + needBuild: NEED_BUILD, + isXpack: IS_XPACK, + ciGroup: CI_GROUP + ) } } @@ -59,14 +51,70 @@ kibanaPipeline(timeoutMinutes: 180) { } } +def agentProcess(Map params = [:]) { + def config = [ + agentNumber: 1, + agentExecutions: 0, + worker: {}, + workerFailures: [], + needBuild: false, + isXpack: false, + ciGroup: null, + ] + params + + catchErrors { + print "Agent ${config.agentNumber} - ${config.agentExecutions} executions" + + withEnv([ + 'IGNORE_SHIP_CI_STATS_ERROR=true', + ]) { + kibanaPipeline.withTasks([ + parallel: 20, + ]) { + task { + if (config.needBuild) { + if (!config.isXpack) { + kibanaPipeline.buildOss() + } else { + kibanaPipeline.buildXpack() + } + } + + for(def i = 0; i < config.agentExecutions; i++) { + def taskNumber = i + task({ + withEnv([ + "REMOVE_KIBANA_INSTALL_DIR=1", + ]) { + catchErrors { + try { + config.worker() + } catch (ex) { + config.workerFailures << "agent-${config.agentNumber}-${taskNumber}" + throw ex + } + } + } + }) + } + } + } + } + } +} + def getWorkerFromParams(isXpack, job, ciGroup) { if (!isXpack) { if (job == 'accessibility') { return kibanaPipeline.functionalTestProcess('kibana-accessibility', './test/scripts/jenkins_accessibility.sh') } else if (job == 'firefoxSmoke') { return kibanaPipeline.functionalTestProcess('firefoxSmoke', './test/scripts/jenkins_firefox_smoke.sh') - } else if(job == 'visualRegression') { + } else if (job == 'visualRegression') { return kibanaPipeline.functionalTestProcess('visualRegression', './test/scripts/jenkins_visual_regression.sh') + } else if (job == 'jestIntegration') { + return kibanaPipeline.scriptTaskDocker('Jest Integration Tests', 'test/scripts/test/jest_integration.sh') + } else if (job == 'apiIntegration') { + return kibanaPipeline.scriptTask('API Integration Tests', 'test/scripts/test/api_integration.sh') } else { return kibanaPipeline.ossCiGroupProcess(ciGroup) } @@ -76,45 +124,16 @@ def getWorkerFromParams(isXpack, job, ciGroup) { return kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh') } else if (job == 'firefoxSmoke') { return kibanaPipeline.functionalTestProcess('xpack-firefoxSmoke', './test/scripts/jenkins_xpack_firefox_smoke.sh') - } else if(job == 'visualRegression') { + } else if (job == 'visualRegression') { return kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh') } else { return kibanaPipeline.xpackCiGroupProcess(ciGroup) } } -def getWorkerMap(agentNumber, numberOfExecutions, worker, workerFailures, maxWorkerProcesses = 12) { - def workerMap = [:] - def numberOfWorkers = Math.min(numberOfExecutions, maxWorkerProcesses) - - for(def i = 1; i <= numberOfWorkers; i++) { - def workerExecutions = floor(numberOfExecutions/numberOfWorkers + (i <= numberOfExecutions%numberOfWorkers ? 1 : 0)) - - workerMap["agent-${agentNumber}-worker-${i}"] = { workerNumber -> - for(def j = 0; j < workerExecutions; j++) { - print "Execute agent-${agentNumber} worker-${workerNumber}: ${j}" - withEnv([ - "REMOVE_KIBANA_INSTALL_DIR=1", - ]) { - catchErrors { - try { - worker(workerNumber) - } catch (ex) { - workerFailures << "agent-${agentNumber} worker-${workerNumber}-${j}" - throw ex - } - } - } - } - } - } - - return workerMap -} - def getAgentCount(executions) { - // Increase agent count every 24 worker processess, up to 3 agents maximum - return Math.min(3, 1 + floor(executions/24)) + // Increase agent count every 20 worker processess, up to 3 agents maximum + return Math.min(3, 1 + floor(executions/20)) } def trunc(str, length) { diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 1fe1d78658669a..466a04d9b6b39f 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -425,12 +425,13 @@ def buildXpackPlugins() { runbld('./test/scripts/jenkins_xpack_build_plugins.sh', 'Build X-Pack Plugins') } -def withTasks(Map params = [worker: [:]], Closure closure) { +def withTasks(Map params = [:], Closure closure) { catchErrors { - def config = [name: 'ci-worker', size: 'xxl', ramDisk: true] + (params.worker ?: [:]) + def config = [setupWork: {}, worker: [:], parallel: 24] + params + def workerConfig = [name: 'ci-worker', size: 'xxl', ramDisk: true] + config.worker - workers.ci(config) { - withCiTaskQueue(parallel: 24) { + workers.ci(workerConfig) { + withCiTaskQueue([parallel: config.parallel]) { parallel([ docker: { retry(2) { @@ -443,6 +444,8 @@ def withTasks(Map params = [worker: [:]], Closure closure) { xpackPlugins: { buildXpackPlugins() }, ]) + config.setupWork() + catchErrors { closure() } From 863a2d06a43ff77baf6c8978925a0cd75946924d Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Thu, 18 Feb 2021 13:58:55 -0600 Subject: [PATCH 60/93] Use correct environment in anomaly detection setup link (#91877) This was still using `uiFilters.environment` instead of environment, so the warning would never show. --- .../application/action_menu/anomaly_detection_setup_link.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx index 0c1eacb6e800ca..3cb31aa10c4feb 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx @@ -30,8 +30,9 @@ export type AnomalyDetectionApiResponse = APIReturnType<'GET /api/apm/settings/a const DEFAULT_DATA = { jobs: [], hasLegacyJobs: false }; export function AnomalyDetectionSetupLink() { - const { uiFilters } = useUrlParams(); - const environment = uiFilters.environment; + const { + urlParams: { environment }, + } = useUrlParams(); const { core } = useApmPluginContext(); const canGetJobs = !!core.application.capabilities.ml?.canGetJobs; const license = useLicenseContext(); From da25d2753b2ec4b608cddf8c2a80a1e7a1f3b4f0 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Thu, 18 Feb 2021 12:36:25 -0800 Subject: [PATCH 61/93] [Alerts][Docs] Added API documentation for alerts plugin (#91067) * Added API documentation for alerts plugin * Added link to user api * fixed links * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/create.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/create.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/create.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/create.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/create.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/create.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/create.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/create.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/create.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/create.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/delete.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/delete.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/disable.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/enable.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/disable.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/update.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/enable.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/find.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/find.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/find.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/find.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/find.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/find.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/get.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/get.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/health.asciidoc Co-authored-by: Gidi Meir Morris * Update docs/api/alerts/health.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/health.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/health.asciidoc Co-authored-by: Gidi Meir Morris * Update docs/api/alerts/health.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/health.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/health.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/list.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/health.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/health.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/list.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/list.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/api/alerts/list.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * fixed due to comments * fixed due to comments * fixed due to comments * fixed links * Apply suggestions from code review Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * fixed due to comments Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: Gidi Meir Morris --- docs/api/alerts.asciidoc | 42 +++++++ docs/api/alerts/create.asciidoc | 189 ++++++++++++++++++++++++++++ docs/api/alerts/delete.asciidoc | 36 ++++++ docs/api/alerts/disable.asciidoc | 34 +++++ docs/api/alerts/enable.asciidoc | 34 +++++ docs/api/alerts/find.asciidoc | 117 +++++++++++++++++ docs/api/alerts/get.asciidoc | 70 +++++++++++ docs/api/alerts/health.asciidoc | 85 +++++++++++++ docs/api/alerts/list.asciidoc | 127 +++++++++++++++++++ docs/api/alerts/mute.asciidoc | 37 ++++++ docs/api/alerts/mute_all.asciidoc | 34 +++++ docs/api/alerts/unmute.asciidoc | 37 ++++++ docs/api/alerts/unmute_all.asciidoc | 34 +++++ docs/api/alerts/update.asciidoc | 134 ++++++++++++++++++++ docs/user/api.asciidoc | 1 + 15 files changed, 1011 insertions(+) create mode 100644 docs/api/alerts.asciidoc create mode 100644 docs/api/alerts/create.asciidoc create mode 100644 docs/api/alerts/delete.asciidoc create mode 100644 docs/api/alerts/disable.asciidoc create mode 100644 docs/api/alerts/enable.asciidoc create mode 100644 docs/api/alerts/find.asciidoc create mode 100644 docs/api/alerts/get.asciidoc create mode 100644 docs/api/alerts/health.asciidoc create mode 100644 docs/api/alerts/list.asciidoc create mode 100644 docs/api/alerts/mute.asciidoc create mode 100644 docs/api/alerts/mute_all.asciidoc create mode 100644 docs/api/alerts/unmute.asciidoc create mode 100644 docs/api/alerts/unmute_all.asciidoc create mode 100644 docs/api/alerts/update.asciidoc diff --git a/docs/api/alerts.asciidoc b/docs/api/alerts.asciidoc new file mode 100644 index 00000000000000..a19c538bcb4d7b --- /dev/null +++ b/docs/api/alerts.asciidoc @@ -0,0 +1,42 @@ +[[alerts-api]] +== Alerts APIs + +The following APIs are available for managing {kib} alerts. + +* <> to create an alert + +* <> to update the attributes for existing alerts + +* <> to retrieve a single alert by ID + +* <> to permanently remove an alert + +* <> to retrieve a paginated set of alerts by condition + +* <> to retrieve a list of all alert types + +* <> to enable a single alert by ID + +* <> to disable a single alert by ID + +* <> to mute alert instances for a single alert by ID + +* <> to unmute alert instances for a single alert by ID + +* <> to unmute all alert instances for a single alert by ID + +* <> to retrieve the health of the alerts framework + +include::alerts/create.asciidoc[] +include::alerts/update.asciidoc[] +include::alerts/get.asciidoc[] +include::alerts/delete.asciidoc[] +include::alerts/find.asciidoc[] +include::alerts/list.asciidoc[] +include::alerts/enable.asciidoc[] +include::alerts/disable.asciidoc[] +include::alerts/mute_all.asciidoc[] +include::alerts/mute.asciidoc[] +include::alerts/unmute_all.asciidoc[] +include::alerts/unmute.asciidoc[] +include::alerts/health.asciidoc[] diff --git a/docs/api/alerts/create.asciidoc b/docs/api/alerts/create.asciidoc new file mode 100644 index 00000000000000..9e188b971c9b54 --- /dev/null +++ b/docs/api/alerts/create.asciidoc @@ -0,0 +1,189 @@ +[[alerts-api-create]] +=== Create alert API +++++ +Create alert +++++ + +Create {kib} alerts. + +[[alerts-api-create-request]] +==== Request + +`POST :/api/alerts/alert` + +[[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":"alert '{{alertName}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{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": "alert {{alertName}} is active for group {{context.group}}:\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{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/alerts/delete.asciidoc b/docs/api/alerts/delete.asciidoc new file mode 100644 index 00000000000000..b51005daae658d --- /dev/null +++ b/docs/api/alerts/delete.asciidoc @@ -0,0 +1,36 @@ +[[alerts-api-delete]] +=== Delete alert API +++++ +Delete alert +++++ + +Permanently remove an alert. + +WARNING: Once you delete an alert, you cannot recover it. + +[[alerts-api-delete-request]] +==== Request + +`DELETE :/api/alerts/alert/` + +[[alerts-api-delete-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the alert that you want to remove. + +[[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/alerts/disable.asciidoc b/docs/api/alerts/disable.asciidoc new file mode 100644 index 00000000000000..5f74c333794095 --- /dev/null +++ b/docs/api/alerts/disable.asciidoc @@ -0,0 +1,34 @@ +[[alerts-api-disable]] +=== Disable alert API +++++ +Disable alert +++++ + +Disable an alert. + +[[alerts-api-disable-request]] +==== Request + +`POST :/api/alerts/alert//_disable` + +[[alerts-api-disable-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the alert that you want to disable. + +[[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/alerts/enable.asciidoc b/docs/api/alerts/enable.asciidoc new file mode 100644 index 00000000000000..a10383f2a440d1 --- /dev/null +++ b/docs/api/alerts/enable.asciidoc @@ -0,0 +1,34 @@ +[[alerts-api-enable]] +=== Enable alert API +++++ +Enable alert +++++ + +Enable an alert. + +[[alerts-api-enable-request]] +==== Request + +`POST :/api/alerts/alert//_enable` + +[[alerts-api-enable-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the alert that you want to enable. + +[[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/alerts/find.asciidoc b/docs/api/alerts/find.asciidoc new file mode 100644 index 00000000000000..97cd9f4c19ba75 --- /dev/null +++ b/docs/api/alerts/find.asciidoc @@ -0,0 +1,117 @@ +[[alerts-api-find]] +=== Find alerts API +++++ +Find alerts +++++ + +Retrieve a paginated set of alerts based on condition. + +[[alerts-api-find-request]] +==== Request + +`GET :/api/alerts/_find` + +[[alerts-api-find-query-params]] +==== Query Parameters + +`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. + +NOTE: 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-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/alerts/get.asciidoc b/docs/api/alerts/get.asciidoc new file mode 100644 index 00000000000000..934d7466dec3d7 --- /dev/null +++ b/docs/api/alerts/get.asciidoc @@ -0,0 +1,70 @@ +[[alerts-api-get]] +=== Get alert API +++++ +Get alert +++++ + +Retrieve an alert by ID. + +[[alerts-api-get-request]] +==== Request + +`GET :/api/alerts/alert/` + +[[alerts-api-get-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the alert to retrieve. + +[[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/alerts/health.asciidoc b/docs/api/alerts/health.asciidoc new file mode 100644 index 00000000000000..3710ccf4249454 --- /dev/null +++ b/docs/api/alerts/health.asciidoc @@ -0,0 +1,85 @@ +[[alerts-api-health]] +=== Get Alerting framework health API +++++ +Get Alerting framework health +++++ + +Retrieve the health status of the Alerting framework. + +[[alerts-api-health-request]] +==== Request + +`GET :/api/alerts/_health` + +[[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, + "alertingFrameworkHeath":{ + "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. + +| `alertingFrameworkHeath` +| This state property has three substates that identify the health of the alerting framework API: `decryptionHealth`, `executionHealth`, and `readHealth`. + +|=== + +`alertingFrameworkHeath` 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/alerts/list.asciidoc b/docs/api/alerts/list.asciidoc new file mode 100644 index 00000000000000..0bc3e158ec2634 --- /dev/null +++ b/docs/api/alerts/list.asciidoc @@ -0,0 +1,127 @@ +[[alerts-api-list]] +=== List alert types API +++++ +List all alert types API +++++ + +Retrieve a list of all alert types. + +[[alerts-api-list-request]] +==== Request + +`GET :/api/alerts/list_alert_types` + +[[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", + "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. + +| `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/alerts/mute.asciidoc b/docs/api/alerts/mute.asciidoc new file mode 100644 index 00000000000000..9279786deae4cf --- /dev/null +++ b/docs/api/alerts/mute.asciidoc @@ -0,0 +1,37 @@ +[[alerts-api-mute]] +=== Mute alert instance API +++++ +Mute alert instance +++++ + +Mute an alert instance. + +[[alerts-api-mute-request]] +==== Request + +`POST :/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. + +[[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/alerts/mute_all.asciidoc b/docs/api/alerts/mute_all.asciidoc new file mode 100644 index 00000000000000..f8a8c137240c6b --- /dev/null +++ b/docs/api/alerts/mute_all.asciidoc @@ -0,0 +1,34 @@ +[[alerts-api-mute-all]] +=== Mute all alert instances API +++++ +Mute all alert instances +++++ + +Mute all alert instances. + +[[alerts-api-mute-all-request]] +==== Request + +`POST :/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. + +[[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/alerts/unmute.asciidoc b/docs/api/alerts/unmute.asciidoc new file mode 100644 index 00000000000000..f091ae3f453257 --- /dev/null +++ b/docs/api/alerts/unmute.asciidoc @@ -0,0 +1,37 @@ +[[alerts-api-unmute]] +=== Unmute alert instance API +++++ +Unmute alert instance +++++ + +Unmute an alert instance. + +[[alerts-api-unmute-request]] +==== Request + +`POST :/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. + +[[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/alerts/unmute_all.asciidoc b/docs/api/alerts/unmute_all.asciidoc new file mode 100644 index 00000000000000..2359d120cf2602 --- /dev/null +++ b/docs/api/alerts/unmute_all.asciidoc @@ -0,0 +1,34 @@ +[[alerts-api-unmute-all]] +=== Unmute all alert instances API +++++ +Unmute all alert instances +++++ + +Unmute all alert instances. + +[[alerts-api-unmute-all-request]] +==== Request + +`POST :/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. + +[[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/alerts/update.asciidoc b/docs/api/alerts/update.asciidoc new file mode 100644 index 00000000000000..aee2dd049a66f7 --- /dev/null +++ b/docs/api/alerts/update.asciidoc @@ -0,0 +1,134 @@ +[[alerts-api-update]] +=== Update alert API +++++ +Update alert +++++ + +Update the attributes for an existing alert. + +[[alerts-api-update-request]] +==== Request + +`PUT :/api/alerts/alert/` + +[[alerts-api-update-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the alert that you want to update. + +[[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/user/api.asciidoc b/docs/user/api.asciidoc index 2ae83bee1e06c7..9916ab42186dc6 100644 --- a/docs/user/api.asciidoc +++ b/docs/user/api.asciidoc @@ -36,6 +36,7 @@ include::{kib-repo-dir}/api/features.asciidoc[] include::{kib-repo-dir}/api/spaces-management.asciidoc[] include::{kib-repo-dir}/api/role-management.asciidoc[] include::{kib-repo-dir}/api/saved-objects.asciidoc[] +include::{kib-repo-dir}/api/alerts.asciidoc[] include::{kib-repo-dir}/api/actions-and-connectors.asciidoc[] include::{kib-repo-dir}/api/dashboard-api.asciidoc[] include::{kib-repo-dir}/api/logstash-configuration-management.asciidoc[] From 0760bfb8701870c0991c853918ae6f981546ce6a Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Thu, 18 Feb 2021 15:34:50 -0600 Subject: [PATCH 62/93] [Fleet] Bootstrap functional test suite (#91898) --- .../components/agent_policy_section.tsx | 2 +- .../overview/components/agent_section.tsx | 2 +- .../components/datastream_section.tsx | 2 +- .../components/integration_section.tsx | 2 +- .../test/fleet_functional/apps/fleet/index.ts | 17 ++++++++ .../apps/fleet/overview_page.ts | 38 +++++++++++++++++ x-pack/test/fleet_functional/config.ts | 41 +++++++++++++++++++ .../ftr_provider_context.d.ts | 13 ++++++ .../fleet_functional/page_objects/index.ts | 14 +++++++ .../page_objects/overview_page.ts | 41 +++++++++++++++++++ .../test/fleet_functional/services/index.ts | 12 ++++++ 11 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 x-pack/test/fleet_functional/apps/fleet/index.ts create mode 100644 x-pack/test/fleet_functional/apps/fleet/overview_page.ts create mode 100644 x-pack/test/fleet_functional/config.ts create mode 100644 x-pack/test/fleet_functional/ftr_provider_context.d.ts create mode 100644 x-pack/test/fleet_functional/page_objects/index.ts create mode 100644 x-pack/test/fleet_functional/page_objects/overview_page.ts create mode 100644 x-pack/test/fleet_functional/services/index.ts diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx index 5bf1a383423b28..c3b59458abf0ac 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx @@ -31,7 +31,7 @@ export const OverviewPolicySection: React.FC<{ agentPolicies: AgentPolicy[] }> = }); return ( - + { const agentStatusRequest = useGetAgentStatus({}); return ( - + { } return ( - + { (item) => 'savedObject' in item && item.version > item.savedObject.attributes.version )?.length ?? 0; return ( - + { + before(async () => { + await overviewPage.navigateToOverview(); + }); + + it('should show the Integrations section', async () => { + await overviewPage.integrationsSectionExistsOrFail(); + }); + + it('should show the Agents section', async () => { + await overviewPage.agentSectionExistsOrFail(); + }); + + it('should show the Agent policies section', async () => { + await overviewPage.agentPolicySectionExistsOrFail(); + }); + + it('should show the Data streams section', async () => { + await overviewPage.datastreamSectionExistsOrFail(); + }); + }); + }); +} diff --git a/x-pack/test/fleet_functional/config.ts b/x-pack/test/fleet_functional/config.ts new file mode 100644 index 00000000000000..386f39d7ec6683 --- /dev/null +++ b/x-pack/test/fleet_functional/config.ts @@ -0,0 +1,41 @@ +/* + * 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 { resolve } from 'path'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { pageObjects } from './page_objects'; +import { services } from './services'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); + + return { + ...xpackFunctionalConfig.getAll(), + pageObjects, + testFiles: [resolve(__dirname, './apps/fleet')], + junit: { + reportName: 'X-Pack Fleet Functional Tests', + }, + services, + apps: { + ...xpackFunctionalConfig.get('apps'), + ['fleet']: { + pathname: '/app/fleet', + }, + }, + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + '--xpack.fleet.enabled=true', + ], + }, + layout: { + fixedHeaderHeight: 200, + }, + }; +} diff --git a/x-pack/test/fleet_functional/ftr_provider_context.d.ts b/x-pack/test/fleet_functional/ftr_provider_context.d.ts new file mode 100644 index 00000000000000..ec28c00e72e47f --- /dev/null +++ b/x-pack/test/fleet_functional/ftr_provider_context.d.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; + +import { pageObjects } from './page_objects'; +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/fleet_functional/page_objects/index.ts b/x-pack/test/fleet_functional/page_objects/index.ts new file mode 100644 index 00000000000000..2c534285146e54 --- /dev/null +++ b/x-pack/test/fleet_functional/page_objects/index.ts @@ -0,0 +1,14 @@ +/* + * 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 { pageObjects as xpackFunctionalPageObjects } from '../../functional/page_objects'; +import { OverviewPage } from './overview_page'; + +export const pageObjects = { + ...xpackFunctionalPageObjects, + overviewPage: OverviewPage, +}; diff --git a/x-pack/test/fleet_functional/page_objects/overview_page.ts b/x-pack/test/fleet_functional/page_objects/overview_page.ts new file mode 100644 index 00000000000000..ca58acd0a7b6a3 --- /dev/null +++ b/x-pack/test/fleet_functional/page_objects/overview_page.ts @@ -0,0 +1,41 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; +import { PLUGIN_ID } from '../../../plugins/fleet/common'; + +// NOTE: import path below should be the deep path to the actual module - else we get CI errors +import { pagePathGetters } from '../../../plugins/fleet/public/applications/fleet/constants/page_paths'; + +export function OverviewPage({ getService, getPageObjects }: FtrProviderContext) { + const pageObjects = getPageObjects(['common']); + const testSubjects = getService('testSubjects'); + + return { + async navigateToOverview() { + await pageObjects.common.navigateToApp(PLUGIN_ID, { + hash: pagePathGetters.overview(), + }); + }, + + async integrationsSectionExistsOrFail() { + await testSubjects.existOrFail('fleet-integrations-section'); + }, + + async agentPolicySectionExistsOrFail() { + await testSubjects.existOrFail('fleet-agent-policy-section'); + }, + + async agentSectionExistsOrFail() { + await testSubjects.existOrFail('fleet-agent-section'); + }, + + async datastreamSectionExistsOrFail() { + await testSubjects.existOrFail('fleet-datastream-section'); + }, + }; +} diff --git a/x-pack/test/fleet_functional/services/index.ts b/x-pack/test/fleet_functional/services/index.ts new file mode 100644 index 00000000000000..f5cfb8a32d34ec --- /dev/null +++ b/x-pack/test/fleet_functional/services/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { services as xPackFunctionalServices } from '../../functional/services'; + +export const services = { + ...xPackFunctionalServices, +}; From 2408d003254f2352037381fdaf5797850fed1551 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 18 Feb 2021 14:42:17 -0700 Subject: [PATCH 63/93] [data.search] Use incrementCounter for search telemetry (#91230) * [data.search] Use incrementCounter for search telemetry * Update reported type * Retry conflicts * Fix telemetry check * Use saved object migration to drop previous document * Review feedback * Fix import Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../saved_objects/migrations/to_v7_12_0.ts | 17 +++++ .../server/saved_objects/search_telemetry.ts | 4 + .../data/server/search/collectors/fetch.ts | 12 ++- .../data/server/search/collectors/register.ts | 10 ++- .../data/server/search/collectors/usage.ts | 73 +++++++++---------- 5 files changed, 70 insertions(+), 46 deletions(-) create mode 100644 src/plugins/data/server/saved_objects/migrations/to_v7_12_0.ts diff --git a/src/plugins/data/server/saved_objects/migrations/to_v7_12_0.ts b/src/plugins/data/server/saved_objects/migrations/to_v7_12_0.ts new file mode 100644 index 00000000000000..955028c0f9bf20 --- /dev/null +++ b/src/plugins/data/server/saved_objects/migrations/to_v7_12_0.ts @@ -0,0 +1,17 @@ +/* + * 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 type { SavedObjectMigrationFn } from 'kibana/server'; + +/** + * Drop the previous document's attributes, which report `averageDuration` incorrectly. + * @param doc + */ +export const migrate712: SavedObjectMigrationFn = (doc) => { + return { ...doc, attributes: {} }; +}; diff --git a/src/plugins/data/server/saved_objects/search_telemetry.ts b/src/plugins/data/server/saved_objects/search_telemetry.ts index 24f884c85b7c53..33ad4b74f31697 100644 --- a/src/plugins/data/server/saved_objects/search_telemetry.ts +++ b/src/plugins/data/server/saved_objects/search_telemetry.ts @@ -7,6 +7,7 @@ */ import { SavedObjectsType } from 'kibana/server'; +import { migrate712 } from './migrations/to_v7_12_0'; export const searchTelemetry: SavedObjectsType = { name: 'search-telemetry', @@ -16,4 +17,7 @@ export const searchTelemetry: SavedObjectsType = { dynamic: false, properties: {}, }, + migrations: { + '7.12.0': migrate712, + }, }; diff --git a/src/plugins/data/server/search/collectors/fetch.ts b/src/plugins/data/server/search/collectors/fetch.ts index 05e5558157b3ce..6dfc29e2cf2a66 100644 --- a/src/plugins/data/server/search/collectors/fetch.ts +++ b/src/plugins/data/server/search/collectors/fetch.ts @@ -11,14 +11,14 @@ import { first } from 'rxjs/operators'; import { SharedGlobalConfig } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { CollectorFetchContext } from 'src/plugins/usage_collection/server'; -import { Usage } from './register'; +import { CollectedUsage, ReportedUsage } from './register'; interface SearchTelemetry { - 'search-telemetry': Usage; + 'search-telemetry': CollectedUsage; } type ESResponse = SearchResponse; export function fetchProvider(config$: Observable) { - return async ({ esClient }: CollectorFetchContext): Promise => { + return async ({ esClient }: CollectorFetchContext): Promise => { const config = await config$.pipe(first()).toPromise(); const { body: esResponse } = await esClient.search( { @@ -37,6 +37,10 @@ export function fetchProvider(config$: Observable) { averageDuration: null, }; } - return esResponse.hits.hits[0]._source['search-telemetry']; + const { successCount, errorCount, totalDuration } = esResponse.hits.hits[0]._source[ + 'search-telemetry' + ]; + const averageDuration = totalDuration / successCount; + return { successCount, errorCount, averageDuration }; }; } diff --git a/src/plugins/data/server/search/collectors/register.ts b/src/plugins/data/server/search/collectors/register.ts index 2a5637d86e1bf9..a370377c30eeaf 100644 --- a/src/plugins/data/server/search/collectors/register.ts +++ b/src/plugins/data/server/search/collectors/register.ts @@ -10,7 +10,13 @@ import { PluginInitializerContext } from 'kibana/server'; import { UsageCollectionSetup } from '../../../../usage_collection/server'; import { fetchProvider } from './fetch'; -export interface Usage { +export interface CollectedUsage { + successCount: number; + errorCount: number; + totalDuration: number; +} + +export interface ReportedUsage { successCount: number; errorCount: number; averageDuration: number | null; @@ -21,7 +27,7 @@ export async function registerUsageCollector( context: PluginInitializerContext ) { try { - const collector = usageCollection.makeUsageCollector({ + const collector = usageCollection.makeUsageCollector({ type: 'search', isReady: () => true, fetch: fetchProvider(context.config.legacy.globalConfig$), diff --git a/src/plugins/data/server/search/collectors/usage.ts b/src/plugins/data/server/search/collectors/usage.ts index c5dc2414c0e808..c9f0a5bf249442 100644 --- a/src/plugins/data/server/search/collectors/usage.ts +++ b/src/plugins/data/server/search/collectors/usage.ts @@ -6,11 +6,13 @@ * Side Public License, v 1. */ +import { once } from 'lodash'; import type { CoreSetup, Logger } from 'kibana/server'; +import { SavedObjectsErrorHelpers } from '../../../../../core/server'; import type { IEsSearchResponse } from '../../../common'; -import type { Usage } from './register'; const SAVED_OBJECT_ID = 'search-telemetry'; +const MAX_RETRY_COUNT = 3; export interface SearchUsage { trackError(): Promise; @@ -18,51 +20,42 @@ export interface SearchUsage { } export function usageProvider(core: CoreSetup): SearchUsage { - const getTracker = (eventType: keyof Usage) => { - return async (duration?: number) => { - const repository = await core - .getStartServices() - .then(([coreStart]) => coreStart.savedObjects.createInternalRepository()); + const getRepository = once(async () => { + const [coreStart] = await core.getStartServices(); + return coreStart.savedObjects.createInternalRepository(); + }); - let attributes: Usage; - let doesSavedObjectExist: boolean = true; - - try { - const response = await repository.get(SAVED_OBJECT_ID, SAVED_OBJECT_ID); - attributes = response.attributes; - } catch (e) { - doesSavedObjectExist = false; - attributes = { - successCount: 0, - errorCount: 0, - averageDuration: 0, - }; - } - - attributes[eventType]++; - - // Only track the average duration for successful requests - if (eventType === 'successCount') { - attributes.averageDuration = - ((duration ?? 0) + (attributes.averageDuration ?? 0)) / (attributes.successCount ?? 1); + const trackSuccess = async (duration: number, retryCount = 0) => { + const repository = await getRepository(); + try { + await repository.incrementCounter(SAVED_OBJECT_ID, SAVED_OBJECT_ID, [ + { fieldName: 'successCount' }, + { + fieldName: 'totalDuration', + incrementBy: duration, + }, + ]); + } catch (e) { + if (SavedObjectsErrorHelpers.isConflictError(e) && retryCount < MAX_RETRY_COUNT) { + setTimeout(() => trackSuccess(duration, retryCount + 1), 1000); } + } + }; - try { - if (doesSavedObjectExist) { - await repository.update(SAVED_OBJECT_ID, SAVED_OBJECT_ID, attributes); - } else { - await repository.create(SAVED_OBJECT_ID, attributes, { id: SAVED_OBJECT_ID }); - } - } catch (e) { - // Version conflict error, swallow + const trackError = async (retryCount = 0) => { + const repository = await getRepository(); + try { + await repository.incrementCounter(SAVED_OBJECT_ID, SAVED_OBJECT_ID, [ + { fieldName: 'errorCount' }, + ]); + } catch (e) { + if (SavedObjectsErrorHelpers.isConflictError(e) && retryCount < MAX_RETRY_COUNT) { + setTimeout(() => trackError(retryCount + 1), 1000); } - }; + } }; - return { - trackError: () => getTracker('errorCount')(), - trackSuccess: getTracker('successCount'), - }; + return { trackSuccess, trackError }; } /** From 0f804677de62e484920a7ef6ce357de2ab624aa9 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Thu, 18 Feb 2021 13:43:03 -0800 Subject: [PATCH 64/93] [Fleet] Silently swallow 404 errors when deleting ingest pipelines (#91778) * Only show transform logs when there are transforms * Silently swallow 404 errors when deleting ingest pipelines * Change to IngestManagerError --- .../epm/elasticsearch/ingest_pipeline/remove.ts | 7 ++++++- .../services/epm/elasticsearch/transform/install.ts | 10 +++++++--- .../services/epm/elasticsearch/transform/remove.ts | 4 +++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts index f12d68190b4acb..4acc4767de5255 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts @@ -8,6 +8,7 @@ import { SavedObjectsClientContract } from 'src/core/server'; import { appContextService } from '../../../'; import { CallESAsCurrentUser, ElasticsearchAssetType } from '../../../../types'; +import { IngestManagerError } from '../../../../errors'; import { getInstallation } from '../../packages/get'; import { PACKAGES_SAVED_OBJECT_TYPE, EsAssetReference } from '../../../../../common'; @@ -61,7 +62,11 @@ export async function deletePipeline(callCluster: CallESAsCurrentUser, id: strin try { await callCluster('ingest.deletePipeline', { id }); } catch (err) { - throw new Error(`error deleting pipeline ${id}`); + // Only throw if error is not a 404 error. Sometimes the pipeline is already deleted, but we have + // duplicate references to them, see https://github.com/elastic/kibana/issues/91192 + if (err.statusCode !== 404) { + throw new IngestManagerError(`error deleting pipeline ${id}: ${err}`); + } } } } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts index 57e1090f8954b4..948a9c56746f36 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts @@ -42,9 +42,13 @@ export const installTransform = async ( previousInstalledTransformEsAssets = installation.installed_es.filter( ({ type, id }) => type === ElasticsearchAssetType.transform ); - logger.info( - `Found previous transform references:\n ${JSON.stringify(previousInstalledTransformEsAssets)}` - ); + if (previousInstalledTransformEsAssets.length) { + logger.info( + `Found previous transform references:\n ${JSON.stringify( + previousInstalledTransformEsAssets + )}` + ); + } } // delete all previous transform diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts index b08b7cb7f1ec8d..0e947e0f0b90bb 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts @@ -26,7 +26,9 @@ export const deleteTransforms = async ( transformIds: string[] ) => { const logger = appContextService.getLogger(); - logger.info(`Deleting currently installed transform ids ${transformIds}`); + if (transformIds.length) { + logger.info(`Deleting currently installed transform ids ${transformIds}`); + } await Promise.all( transformIds.map(async (transformId) => { // get the index the transform From fe35e0de3b337e47bf36f5af8bdc2a00e437f9af Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Thu, 18 Feb 2021 18:45:15 -0500 Subject: [PATCH 65/93] [Fleet] Install Elastic Agent integration by default during setup (#91676) --- x-pack/plugins/fleet/common/constants/epm.ts | 1 + .../test/fleet_api_integration/apis/fleet_setup.ts | 14 ++++++++++++++ x-pack/test/fleet_api_integration/config.ts | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index b223139803257f..aa17b16b3763ce 100644 --- a/x-pack/plugins/fleet/common/constants/epm.ts +++ b/x-pack/plugins/fleet/common/constants/epm.ts @@ -15,6 +15,7 @@ export const FLEET_SERVER_PACKAGE = 'fleet_server'; export const requiredPackages = { System: 'system', Endpoint: 'endpoint', + ElasticAgent: 'elastic_agent', } as const; // these are currently identical. we can separate if they later diverge diff --git a/x-pack/test/fleet_api_integration/apis/fleet_setup.ts b/x-pack/test/fleet_api_integration/apis/fleet_setup.ts index 31d620cd34931f..d9f55d9fa0b740 100644 --- a/x-pack/test/fleet_api_integration/apis/fleet_setup.ts +++ b/x-pack/test/fleet_api_integration/apis/fleet_setup.ts @@ -105,5 +105,19 @@ export default function (providerContext: FtrProviderContext) { transient_metadata: { enabled: true }, }); }); + + it('should install default packages', async () => { + await supertest.post(`/api/fleet/setup`).set('kbn-xsrf', 'xxxx').expect(200); + + const { body: apiResponse } = await supertest + .get(`/api/fleet/epm/packages?experimental=true`) + .expect(200); + const installedPackages = apiResponse.response + .filter((p: any) => p.status === 'installed') + .map((p: any) => p.name) + .sort(); + + expect(installedPackages).to.eql(['elastic_agent', 'endpoint', 'system']); + }); }); } diff --git a/x-pack/test/fleet_api_integration/config.ts b/x-pack/test/fleet_api_integration/config.ts index 444b8c3a687765..b4833d96c407e4 100644 --- a/x-pack/test/fleet_api_integration/config.ts +++ b/x-pack/test/fleet_api_integration/config.ts @@ -15,7 +15,7 @@ import { defineDockerServersConfig } from '@kbn/test'; // example: https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/detail/snapshot/74/pipeline/257#step-302-log-1. // It should be updated any time there is a new Docker image published for the Snapshot Distribution of the Package Registry. export const dockerImage = - 'docker.elastic.co/package-registry/distribution:5314869e2f6bc01d37b8652f7bda89248950b3a4'; + 'docker.elastic.co/package-registry/distribution:99dadb957d76b704637150d34a7219345cc0aeef'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); From 539f33e53beb9847f2ff4136ea8bba8519c9f375 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 18 Feb 2021 18:43:43 -0600 Subject: [PATCH 66/93] Revert "[SOM] fix flaky suites (#91809)" This reverts commit 386afdca8ffc8b5c61aa0c2ce0ea1a3476fd0aa7. --- test/functional/apps/management/_import_objects.ts | 1 + .../apps/saved_objects_management/edit_saved_object.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts index a3daaf86294939..ca8d8c392ce49b 100644 --- a/test/functional/apps/management/_import_objects.ts +++ b/test/functional/apps/management/_import_objects.ts @@ -23,6 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const log = getService('log'); + // FLAKY: https://github.com/elastic/kibana/issues/89478 describe('import objects', function describeIndexTests() { describe('.ndjson file', () => { beforeEach(async function () { diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts index 89889088bd73ba..81569c5bfc498a 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -55,7 +55,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await button.click(); }; - describe('saved objects edition page', () => { + // Flaky: https://github.com/elastic/kibana/issues/68400 + describe.skip('saved objects edition page', () => { beforeEach(async () => { await esArchiver.load('saved_objects_management/edit_saved_object'); }); From a1644112868b226f36661ac258716542558efb46 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Thu, 18 Feb 2021 21:00:19 -0500 Subject: [PATCH 67/93] [CI] backportrc can skip CI (#91886) --- vars/prChanges.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/vars/prChanges.groovy b/vars/prChanges.groovy index d082672c065a8c..8484df82102592 100644 --- a/vars/prChanges.groovy +++ b/vars/prChanges.groovy @@ -14,6 +14,7 @@ def getSkippablePaths() { /^.ci\/Jenkinsfile_[^\/]+$/, /^\.github\//, /\.md$/, + /^\.backportrc\.json$/ ] } From 8d9ac0058fbaba325932344f35186d1a7e39745c Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Thu, 18 Feb 2021 21:25:01 -0700 Subject: [PATCH 68/93] [Security Solutions] Fixes Cypress tests for indicator match by making the selectors more specific (#91947) ## Summary Fixes the indicator match rules cypress e2e tests by making the selectors more specific. Previously other rules and forms code which live on the DOM beside the indicator match rules could interfere when moving around on the DOM. Now with more specific selectors this should be less likely to happen. If it does happen again I will make the selectors even more specific. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../indicator_match_rule.spec.ts | 3 +-- .../cypress/screens/create_new_rule.ts | 15 ++++++++++++ .../cypress/tasks/create_new_rule.ts | 24 ++++++++++++------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts index bc52be678347aa..db29f44ceb98c8 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts @@ -98,8 +98,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { DETECTIONS_URL, RULE_CREATION } from '../../urls/navigation'; -// Skipped for 7.12 FF - flaky tests -describe.skip('indicator match', () => { +describe('indicator match', () => { describe('Detection rules, Indicator Match', () => { const expectedUrls = newThreatIndicatorRule.referenceUrls.join(''); const expectedFalsePositives = newThreatIndicatorRule.falsePositivesExamples.join(''); diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index a2fb94e462023e..3c7da2e2988475 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -36,9 +36,24 @@ export const CREATE_AND_ACTIVATE_BTN = '[data-test-subj="create-activate"]'; export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]'; +export const THREAT_MAPPING_COMBO_BOX_INPUT = + '[data-test-subj="threatMatchInput"] [data-test-subj="fieldAutocompleteComboBox"]'; + +export const THREAT_MATCH_CUSTOM_QUERY_INPUT = + '[data-test-subj="detectionEngineStepDefineRuleQueryBar"] [data-test-subj="queryInput"]'; + +export const THREAT_MATCH_INDICATOR_QUERY_INPUT = + '[data-test-subj="detectionEngineStepDefineRuleThreatMatchIndices"] [data-test-subj="queryInput"]'; + export const THREAT_MATCH_QUERY_INPUT = '[data-test-subj="detectionEngineStepDefineThreatRuleQueryBar"] [data-test-subj="queryInput"]'; +export const THREAT_MATCH_INDICATOR_INDEX = + '[data-test-subj="detectionEngineStepDefineRuleIndices"] [data-test-subj="comboBoxInput"]'; + +export const THREAT_MATCH_INDICATOR_INDICATOR_INDEX = + '[data-test-subj="detectionEngineStepDefineRuleThreatMatchIndices"] [data-test-subj="comboBoxInput"]'; + export const THREAT_MATCH_AND_BUTTON = '[data-test-subj="andButton"]'; export const THREAT_ITEM_ENTRY_DELETE_BUTTON = '[data-test-subj="itemEntryDeleteButton"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 02ba3937ed542a..11d98f7b808edb 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -80,6 +80,11 @@ import { CUSTOM_QUERY_REQUIRED, RULES_CREATION_FORM, RULES_CREATION_PREVIEW, + THREAT_MATCH_INDICATOR_INDEX, + THREAT_MATCH_INDICATOR_INDICATOR_INDEX, + THREAT_MATCH_CUSTOM_QUERY_INPUT, + THREAT_MATCH_QUERY_INPUT, + THREAT_MAPPING_COMBO_BOX_INPUT, } from '../screens/create_new_rule'; import { TOAST_ERROR } from '../screens/shared'; import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; @@ -325,17 +330,17 @@ export const fillIndicatorMatchRow = ({ }) => { const computedRowNumber = rowNumber == null ? 1 : rowNumber; const computedValueRows = validColumns == null ? 'both' : validColumns; - const OFFSET = 2; - cy.get(COMBO_BOX_INPUT) - .eq(computedRowNumber * OFFSET + 1) + cy.get(THREAT_MAPPING_COMBO_BOX_INPUT) + .eq(computedRowNumber * 2 - 2) + .eq(0) .type(indexField); if (computedValueRows === 'indexField' || computedValueRows === 'both') { cy.get(`button[title="${indexField}"]`) .should('be.visible') .then(([e]) => e.click()); } - cy.get(COMBO_BOX_INPUT) - .eq(computedRowNumber * OFFSET + 2) + cy.get(THREAT_MAPPING_COMBO_BOX_INPUT) + .eq(computedRowNumber * 2 - 1) .type(indicatorIndexField); if (computedValueRows === 'indicatorField' || computedValueRows === 'both') { @@ -393,19 +398,20 @@ export const getAboutContinueButton = () => cy.get(ABOUT_CONTINUE_BTN); export const getDefineContinueButton = () => cy.get(DEFINE_CONTINUE_BUTTON); /** Returns the indicator index pattern */ -export const getIndicatorIndex = () => cy.get(COMBO_BOX_INPUT).eq(0); +export const getIndicatorIndex = () => cy.get(THREAT_MATCH_INDICATOR_INDEX).eq(0); /** Returns the indicator's indicator index */ -export const getIndicatorIndicatorIndex = () => cy.get(COMBO_BOX_INPUT).eq(2); +export const getIndicatorIndicatorIndex = () => + cy.get(THREAT_MATCH_INDICATOR_INDICATOR_INDEX).eq(0); /** Returns the index pattern's clear button */ export const getIndexPatternClearButton = () => cy.get(COMBO_BOX_CLEAR_BTN); /** Returns the custom query input */ -export const getCustomQueryInput = () => cy.get(CUSTOM_QUERY_INPUT).eq(0); +export const getCustomQueryInput = () => cy.get(THREAT_MATCH_CUSTOM_QUERY_INPUT).eq(0); /** Returns the custom query input */ -export const getCustomIndicatorQueryInput = () => cy.get(CUSTOM_QUERY_INPUT).eq(1); +export const getCustomIndicatorQueryInput = () => cy.get(THREAT_MATCH_QUERY_INPUT).eq(0); /** Returns custom query required content */ export const getCustomQueryInvalidationText = () => cy.contains(CUSTOM_QUERY_REQUIRED); From 494d1decf5a5c595ea034a335e0116d9ba76330a Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 19 Feb 2021 11:54:28 +0200 Subject: [PATCH 69/93] [Indexpattern management] Use indexPatterns Service instead of savedObjects client (#91839) * [Index pattern management] Use indexPatterns Service instead of savedObjects client * Minor fixes * Keep the same test setup --- .../step_index_pattern.test.tsx | 4 +- .../step_index_pattern/step_index_pattern.tsx | 19 +---- .../edit_index_pattern/edit_index_pattern.tsx | 9 +-- .../index_pattern_table.tsx | 14 +--- .../public/components/utils.test.ts | 39 +++++----- .../public/components/utils.ts | 76 +++++++++---------- .../mount_management_section.tsx | 3 +- .../index_pattern_management/public/mocks.ts | 10 +-- .../index_pattern_management/public/types.ts | 2 - 9 files changed, 62 insertions(+), 114 deletions(-) diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx index ac025dba95bcd3..8b4f751a4e3a3d 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx @@ -7,7 +7,6 @@ */ import React from 'react'; -import { SavedObjectsFindResponsePublic } from 'kibana/public'; import { StepIndexPattern, canPreselectTimeField } from './step_index_pattern'; import { Header } from './components/header'; import { IndexPatternCreationConfig } from '../../../../../../../plugins/index_pattern_management/public'; @@ -43,8 +42,7 @@ const goToNextStep = () => {}; const mockContext = mockManagementPlugin.createIndexPatternManagmentContext(); -mockContext.savedObjects.client.find = async () => - Promise.resolve(({ savedObjects: [] } as unknown) as SavedObjectsFindResponsePublic); +mockContext.data.indexPatterns.getTitles = async () => Promise.resolve([]); mockContext.uiSettings.get.mockReturnValue(''); describe('StepIndexPattern', () => { diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx index d7038a754fc6be..052e454041181e 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx @@ -10,11 +10,7 @@ import React, { Component } from 'react'; import { EuiSpacer, EuiCallOut, EuiSwitchEvent } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - indexPatterns, - IndexPatternAttributes, - UI_SETTINGS, -} from '../../../../../../../plugins/data/public'; +import { indexPatterns, UI_SETTINGS } from '../../../../../../../plugins/data/public'; import { getIndices, containsIllegalCharacters, @@ -118,18 +114,7 @@ export class StepIndexPattern extends Component { - const { - savedObjects, - } = await this.context.services.savedObjects.client.find({ - type: 'index-pattern', - fields: ['title'], - perPage: 10000, - }); - - const existingIndexPatterns = savedObjects.map((obj) => - obj && obj.attributes ? obj.attributes.title : '' - ) as string[]; - + const existingIndexPatterns = await this.context.services.data.indexPatterns.getTitles(); this.setState({ existingIndexPatterns }); }; diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx index 30edc430f6b959..e314c00bc8176f 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx @@ -26,8 +26,6 @@ import { useKibana } from '../../../../../plugins/kibana_react/public'; import { IndexPatternManagmentContext } from '../../types'; import { Tabs } from './tabs'; import { IndexHeader } from './index_header'; -import { IndexPatternTableItem } from '../types'; -import { getIndexPatterns } from '../utils'; export interface EditIndexPatternProps extends RouteComponentProps { indexPattern: IndexPattern; @@ -62,7 +60,6 @@ export const EditIndexPattern = withRouter( uiSettings, indexPatternManagementStart, overlays, - savedObjects, chrome, data, } = useKibana().services; @@ -97,11 +94,7 @@ export const EditIndexPattern = withRouter( const removePattern = () => { async function doRemove() { if (indexPattern.id === defaultIndex) { - const indexPatterns: IndexPatternTableItem[] = await getIndexPatterns( - savedObjects.client, - uiSettings.get('defaultIndex'), - indexPatternManagementStart - ); + const indexPatterns = await data.indexPatterns.getIdsWithTitle(); uiSettings.remove('defaultIndex'); const otherPatterns = filter(indexPatterns, (pattern) => { return pattern.id !== indexPattern.id; diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx index a2c30ea2884459..b09246b5af8adc 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx +++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx @@ -69,13 +69,13 @@ interface Props extends RouteComponentProps { export const IndexPatternTable = ({ canSave, history }: Props) => { const { setBreadcrumbs, - savedObjects, uiSettings, indexPatternManagementStart, chrome, docLinks, application, http, + data, getMlCardState, } = useKibana().services; const [indexPatterns, setIndexPatterns] = useState([]); @@ -92,21 +92,15 @@ export const IndexPatternTable = ({ canSave, history }: Props) => { history.push ); const gettedIndexPatterns: IndexPatternTableItem[] = await getIndexPatterns( - savedObjects.client, uiSettings.get('defaultIndex'), - indexPatternManagementStart + indexPatternManagementStart, + data.indexPatterns ); setIsLoadingIndexPatterns(false); setCreationOptions(options); setIndexPatterns(gettedIndexPatterns); })(); - }, [ - history.push, - indexPatterns.length, - indexPatternManagementStart, - uiSettings, - savedObjects.client, - ]); + }, [history.push, indexPatterns.length, indexPatternManagementStart, uiSettings, data]); const removeAliases = (item: MatchedItem) => !((item as unknown) as ResolveIndexResponseItemAlias).indices; diff --git a/src/plugins/index_pattern_management/public/components/utils.test.ts b/src/plugins/index_pattern_management/public/components/utils.test.ts index a1e60a4507b3c9..15e0a65390f4d3 100644 --- a/src/plugins/index_pattern_management/public/components/utils.test.ts +++ b/src/plugins/index_pattern_management/public/components/utils.test.ts @@ -5,36 +5,33 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import { IndexPatternsContract } from 'src/plugins/data/public'; import { getIndexPatterns } from './utils'; -import { coreMock } from '../../../../core/public/mocks'; import { mockManagementPlugin } from '../mocks'; -const { savedObjects } = coreMock.createStart(); -const mockManagementPluginStart = mockManagementPlugin.createStartContract(); - -(savedObjects.client.find as jest.Mock).mockResolvedValue({ - savedObjects: [ - { - id: 'test', - get: () => { - return 'test name'; +const indexPatternContractMock = ({ + getIdsWithTitle: jest.fn().mockReturnValue( + Promise.resolve([ + { + id: 'test', + title: 'test name', }, - }, - { - id: 'test1', - get: () => { - return 'test name 1'; + { + id: 'test1', + title: 'test name 1', }, - }, - ], -}); + ]) + ), + get: jest.fn().mockReturnValue(Promise.resolve({})), +} as unknown) as jest.Mocked; + +const mockManagementPluginStart = mockManagementPlugin.createStartContract(); test('getting index patterns', async () => { const indexPatterns = await getIndexPatterns( - savedObjects.client, 'test', - mockManagementPluginStart + mockManagementPluginStart, + indexPatternContractMock ); expect(indexPatterns).toMatchSnapshot(); }); diff --git a/src/plugins/index_pattern_management/public/components/utils.ts b/src/plugins/index_pattern_management/public/components/utils.ts index 59766e398e54e3..5701a1e3752044 100644 --- a/src/plugins/index_pattern_management/public/components/utils.ts +++ b/src/plugins/index_pattern_management/public/components/utils.ts @@ -6,54 +6,46 @@ * Side Public License, v 1. */ -import { IIndexPattern } from 'src/plugins/data/public'; -import { SavedObjectsClientContract } from 'src/core/public'; +import { IndexPatternsContract } from 'src/plugins/data/public'; import { IndexPatternManagementStart } from '../plugin'; export async function getIndexPatterns( - savedObjectsClient: SavedObjectsClientContract, defaultIndex: string, - indexPatternManagementStart: IndexPatternManagementStart + indexPatternManagementStart: IndexPatternManagementStart, + indexPatternsService: IndexPatternsContract ) { - return ( - savedObjectsClient - .find({ - type: 'index-pattern', - fields: ['title', 'type'], - perPage: 10000, - }) - .then((response) => - response.savedObjects - .map((pattern) => { - const id = pattern.id; - const title = pattern.get('title'); - const isDefault = defaultIndex === id; + const existingIndexPatterns = await indexPatternsService.getIdsWithTitle(); + const indexPatternsListItems = await Promise.all( + existingIndexPatterns.map(async ({ id, title }) => { + const isDefault = defaultIndex === id; + const pattern = await indexPatternsService.get(id); + const tags = (indexPatternManagementStart as IndexPatternManagementStart).list.getIndexPatternTags( + pattern, + isDefault + ); - const tags = (indexPatternManagementStart as IndexPatternManagementStart).list.getIndexPatternTags( - pattern, - isDefault - ); + return { + id, + title, + default: isDefault, + tags, + // the prepending of 0 at the default pattern takes care of prioritization + // so the sorting will but the default index on top + // or on bottom of a the table + sort: `${isDefault ? '0' : '1'}${title}`, + }; + }) + ); - return { - id, - title, - default: isDefault, - tags, - // the prepending of 0 at the default pattern takes care of prioritization - // so the sorting will but the default index on top - // or on bottom of a the table - sort: `${isDefault ? '0' : '1'}${title}`, - }; - }) - .sort((a, b) => { - if (a.sort < b.sort) { - return -1; - } else if (a.sort > b.sort) { - return 1; - } else { - return 0; - } - }) - ) || [] + return ( + indexPatternsListItems.sort((a, b) => { + if (a.sort < b.sort) { + return -1; + } else if (a.sort > b.sort) { + return 1; + } else { + return 0; + } + }) || [] ); } diff --git a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx index e47f60ad6fcdd6..355f529fe0f759 100644 --- a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx +++ b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx @@ -41,7 +41,7 @@ export async function mountManagementSection( getMlCardState: () => MlCardState ) { const [ - { chrome, application, savedObjects, uiSettings, notifications, overlays, http, docLinks }, + { chrome, application, uiSettings, notifications, overlays, http, docLinks }, { data, indexPatternFieldEditor }, indexPatternManagementStart, ] = await getStartServices(); @@ -54,7 +54,6 @@ export async function mountManagementSection( const deps: IndexPatternManagmentContext = { chrome, application, - savedObjects, uiSettings, notifications, overlays, diff --git a/src/plugins/index_pattern_management/public/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts index 309d5a5611cd62..606f9edafbca97 100644 --- a/src/plugins/index_pattern_management/public/mocks.ts +++ b/src/plugins/index_pattern_management/public/mocks.ts @@ -75,14 +75,7 @@ const docLinks = { const createIndexPatternManagmentContext = (): { [key in keyof IndexPatternManagmentContext]: any; } => { - const { - chrome, - application, - savedObjects, - uiSettings, - notifications, - overlays, - } = coreMock.createStart(); + const { chrome, application, uiSettings, notifications, overlays } = coreMock.createStart(); const { http } = coreMock.createSetup(); const data = dataPluginMock.createStartContract(); const indexPatternFieldEditor = indexPatternFieldEditorPluginMock.createStartContract(); @@ -90,7 +83,6 @@ const createIndexPatternManagmentContext = (): { return { chrome, application, - savedObjects, uiSettings, notifications, overlays, diff --git a/src/plugins/index_pattern_management/public/types.ts b/src/plugins/index_pattern_management/public/types.ts index 62ee18ababc0bd..58a138df633fd3 100644 --- a/src/plugins/index_pattern_management/public/types.ts +++ b/src/plugins/index_pattern_management/public/types.ts @@ -11,7 +11,6 @@ import { ApplicationStart, IUiSettingsClient, OverlayStart, - SavedObjectsStart, NotificationsStart, DocLinksStart, HttpSetup, @@ -25,7 +24,6 @@ import { IndexPatternFieldEditorStart } from '../../index_pattern_field_editor/p export interface IndexPatternManagmentContext { chrome: ChromeStart; application: ApplicationStart; - savedObjects: SavedObjectsStart; uiSettings: IUiSettingsClient; notifications: NotificationsStart; overlays: OverlayStart; From bf7fdfc87cb04ecb5f6081d9056102d707997e9b Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 19 Feb 2021 11:30:40 +0100 Subject: [PATCH 70/93] [Lens] Pass used histogram interval to chart (#91370) --- ...ibana-plugin-plugins-data-public.search.md | 1 + .../search/aggs/buckets/histogram.test.ts | 22 +++++ .../common/search/aggs/buckets/histogram.ts | 42 ++++++-- .../get_number_histogram_interval.test.ts | 98 +++++++++++++++++++ .../utils/get_number_histogram_interval.ts | 28 ++++++ .../data/common/search/aggs/utils/index.ts | 1 + src/plugins/data/public/index.ts | 2 + src/plugins/data/public/public.api.md | 31 +++--- .../__snapshots__/expression.test.tsx.snap | 49 ++++++++++ .../xy_visualization/expression.test.tsx | 43 +++++++- .../public/xy_visualization/expression.tsx | 29 +++--- 11 files changed, 308 insertions(+), 38 deletions(-) create mode 100644 src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.test.ts create mode 100644 src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md index 4b3c915b49c2dc..440fd25993d643 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md @@ -46,6 +46,7 @@ search: { boundLabel: string; intervalLabel: string; })[]; + getNumberHistogramIntervalByDatatableColumn: (column: import("../../expressions").DatatableColumn) => number | undefined; }; getRequestInspectorStats: typeof getRequestInspectorStats; getResponseInspectorStats: typeof getResponseInspectorStats; diff --git a/src/plugins/data/common/search/aggs/buckets/histogram.test.ts b/src/plugins/data/common/search/aggs/buckets/histogram.test.ts index bddc7060af4401..23693eaf5fca52 100644 --- a/src/plugins/data/common/search/aggs/buckets/histogram.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/histogram.test.ts @@ -12,6 +12,7 @@ import { AggTypesDependencies } from '../agg_types'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketHistogramAggConfig, getHistogramBucketAgg, AutoBounds } from './histogram'; import { BucketAggType } from './bucket_agg_type'; +import { SerializableState } from 'src/plugins/expressions/common'; describe('Histogram Agg', () => { let aggTypesDependencies: AggTypesDependencies; @@ -230,6 +231,27 @@ describe('Histogram Agg', () => { expect(params.interval).toBeNaN(); }); + test('will serialize the auto interval along with the actually chosen interval and deserialize correctly', () => { + const aggConfigs = getAggConfigs({ + interval: 'auto', + field: { + name: 'field', + }, + }); + (aggConfigs.aggs[0] as IBucketHistogramAggConfig).setAutoBounds({ min: 0, max: 1000 }); + const serializedAgg = aggConfigs.aggs[0].serialize(); + const serializedIntervalParam = (serializedAgg.params as SerializableState).used_interval; + expect(serializedIntervalParam).toBe(500); + const freshHistogramAggConfig = getAggConfigs({ + interval: 100, + field: { + name: 'field', + }, + }).aggs[0]; + freshHistogramAggConfig.setParams(serializedAgg.params); + expect(freshHistogramAggConfig.getParam('interval')).toEqual('auto'); + }); + describe('interval scaling', () => { const getInterval = ( maxBars: number, diff --git a/src/plugins/data/common/search/aggs/buckets/histogram.ts b/src/plugins/data/common/search/aggs/buckets/histogram.ts index 5d6d7d509f08e8..e04ebfe494ba91 100644 --- a/src/plugins/data/common/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/common/search/aggs/buckets/histogram.ts @@ -8,6 +8,7 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { IUiSettingsClient } from 'kibana/public'; import { KBN_FIELD_TYPES, UI_SETTINGS } from '../../../../common'; import { AggTypesDependencies } from '../agg_types'; @@ -39,6 +40,7 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig { export interface AggParamsHistogram extends BaseAggParams { field: string; interval: number | string; + used_interval?: number | string; maxBars?: number; intervalBase?: number; min_doc_count?: boolean; @@ -141,17 +143,22 @@ export const getHistogramBucketAgg = ({ }); }, write(aggConfig, output) { - const values = aggConfig.getAutoBounds(); - - output.params.interval = calculateHistogramInterval({ - values, - interval: aggConfig.params.interval, - maxBucketsUiSettings: getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS), - maxBucketsUserInput: aggConfig.params.maxBars, - intervalBase: aggConfig.params.intervalBase, - esTypes: aggConfig.params.field?.spec?.esTypes || [], - }); + output.params.interval = calculateInterval(aggConfig, getConfig); + }, + }, + { + name: 'used_interval', + default: autoInterval, + shouldShow() { + return false; }, + write: () => {}, + serialize(val, aggConfig) { + if (!aggConfig) return undefined; + // store actually used auto interval in serialized agg config to be able to read it from the result data table meta information + return calculateInterval(aggConfig, getConfig); + }, + toExpressionAst: () => undefined, }, { name: 'maxBars', @@ -193,3 +200,18 @@ export const getHistogramBucketAgg = ({ }, ], }); + +function calculateInterval( + aggConfig: IBucketHistogramAggConfig, + getConfig: IUiSettingsClient['get'] +): any { + const values = aggConfig.getAutoBounds(); + return calculateHistogramInterval({ + values, + interval: aggConfig.params.interval, + maxBucketsUiSettings: getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS), + maxBucketsUserInput: aggConfig.params.maxBars, + intervalBase: aggConfig.params.intervalBase, + esTypes: aggConfig.params.field?.spec?.esTypes || [], + }); +} diff --git a/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.test.ts b/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.test.ts new file mode 100644 index 00000000000000..9b08426b551aa3 --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.test.ts @@ -0,0 +1,98 @@ +/* + * 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 { getNumberHistogramIntervalByDatatableColumn } from '.'; +import { BUCKET_TYPES } from '../buckets'; + +describe('getNumberHistogramIntervalByDatatableColumn', () => { + it('returns nothing on column from other data source', () => { + expect( + getNumberHistogramIntervalByDatatableColumn({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'essql', + }, + }) + ).toEqual(undefined); + }); + + it('returns nothing on non histogram column', () => { + expect( + getNumberHistogramIntervalByDatatableColumn({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: BUCKET_TYPES.TERMS, + }, + }, + }) + ).toEqual(undefined); + }); + + it('returns interval on resolved auto interval', () => { + expect( + getNumberHistogramIntervalByDatatableColumn({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: BUCKET_TYPES.HISTOGRAM, + params: { + interval: 'auto', + used_interval: 20, + }, + }, + }, + }) + ).toEqual(20); + }); + + it('returns interval on fixed interval', () => { + expect( + getNumberHistogramIntervalByDatatableColumn({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: BUCKET_TYPES.HISTOGRAM, + params: { + interval: 7, + used_interval: 7, + }, + }, + }, + }) + ).toEqual(7); + }); + + it('returns undefined if information is not available', () => { + expect( + getNumberHistogramIntervalByDatatableColumn({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: BUCKET_TYPES.HISTOGRAM, + params: {}, + }, + }, + }) + ).toEqual(undefined); + }); +}); diff --git a/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.ts b/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.ts new file mode 100644 index 00000000000000..e1c0cf2d69c60c --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/get_number_histogram_interval.ts @@ -0,0 +1,28 @@ +/* + * 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 { DatatableColumn } from 'src/plugins/expressions/common'; +import type { AggParamsHistogram } from '../buckets'; +import { BUCKET_TYPES } from '../buckets/bucket_agg_types'; + +/** + * Helper function returning the used interval for data table column created by the histogramm agg type. + * "auto" will get expanded to the actually used interval. + * If the column is not a column created by a histogram aggregation of the esaggs data source, + * this function will return undefined. + */ +export const getNumberHistogramIntervalByDatatableColumn = (column: DatatableColumn) => { + if (column.meta.source !== 'esaggs') return; + if (column.meta.sourceParams?.type !== BUCKET_TYPES.HISTOGRAM) return; + const params = (column.meta.sourceParams.params as unknown) as AggParamsHistogram; + + if (!params.used_interval || typeof params.used_interval === 'string') { + return undefined; + } + return params.used_interval; +}; diff --git a/src/plugins/data/common/search/aggs/utils/index.ts b/src/plugins/data/common/search/aggs/utils/index.ts index 40451a0f66e0c9..f90e8f88546f46 100644 --- a/src/plugins/data/common/search/aggs/utils/index.ts +++ b/src/plugins/data/common/search/aggs/utils/index.ts @@ -7,6 +7,7 @@ */ export * from './calculate_auto_time_expression'; +export { getNumberHistogramIntervalByDatatableColumn } from './get_number_histogram_interval'; export * from './date_interval_utils'; export * from './get_format_with_aggs'; export * from './ipv4_address'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index df799ede08a310..00bf0385487d81 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -308,6 +308,7 @@ import { parseInterval, toAbsoluteDates, boundsDescendingRaw, + getNumberHistogramIntervalByDatatableColumn, // expressions utils getRequestInspectorStats, getResponseInspectorStats, @@ -417,6 +418,7 @@ export const search = { termsAggFilter, toAbsoluteDates, boundsDescendingRaw, + getNumberHistogramIntervalByDatatableColumn, }, getRequestInspectorStats, getResponseInspectorStats, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 67423295dfe5e3..36e34479ad2d1f 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2238,6 +2238,7 @@ export const search: { boundLabel: string; intervalLabel: string; })[]; + getNumberHistogramIntervalByDatatableColumn: (column: import("../../expressions").DatatableColumn) => number | undefined; }; getRequestInspectorStats: typeof getRequestInspectorStats; getResponseInspectorStats: typeof getResponseInspectorStats; @@ -2649,21 +2650,21 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:417:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:425:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:426:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/search/session/session_service.ts:42:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap index a2047b7bae669b..9a32f1c3311522 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap @@ -26,6 +26,13 @@ exports[`xy_expression XYChart component it renders area 1`] = ` "headerFormatter": [Function], } } + xDomain={ + Object { + "max": undefined, + "min": undefined, + "minInterval": 50, + } + } /> { }} /> ); - expect(component.find(Settings).prop('xDomain')).toBeUndefined(); + const xDomain = component.find(Settings).prop('xDomain'); + expect(xDomain).toEqual( + expect.objectContaining({ + min: undefined, + max: undefined, + }) + ); + }); + + test('it uses min interval if passed in', () => { + const { data, args } = sampleArgs(); + + const component = shallow( + + ); + expect(component.find(Settings).prop('xDomain')).toEqual({ minInterval: 101 }); }); test('it renders bar', () => { @@ -1881,6 +1904,24 @@ describe('xy_expression', () => { expect(result).toEqual(5 * 60 * 1000); }); + it('should return interval of number histogram if available on first x axis columns', async () => { + xyProps.args.layers[0].xScaleType = 'linear'; + xyProps.data.tables.first.columns[2].meta = { + source: 'esaggs', + type: 'number', + field: 'someField', + sourceParams: { + type: 'histogram', + params: { + interval: 'auto', + used_interval: 5, + }, + }, + }; + const result = await calculateMinInterval(xyProps, jest.fn().mockResolvedValue(undefined)); + expect(result).toEqual(5); + }); + it('should return undefined if data table is empty', async () => { xyProps.data.tables.first.rows = []; const result = await calculateMinInterval( diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 7f6414b40cb90e..eda08715b394e9 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -199,13 +199,20 @@ export async function calculateMinInterval( const filteredLayers = getFilteredLayers(layers, data); if (filteredLayers.length === 0) return; const isTimeViz = data.dateRange && filteredLayers.every((l) => l.xScaleType === 'time'); - - if (!isTimeViz) return; - const dateColumn = data.tables[filteredLayers[0].layerId].columns.find( + const xColumn = data.tables[filteredLayers[0].layerId].columns.find( (column) => column.id === filteredLayers[0].xAccessor ); - if (!dateColumn) return; - const dateMetaData = await getIntervalByColumn(dateColumn); + + if (!xColumn) return; + if (!isTimeViz) { + const histogramInterval = search.aggs.getNumberHistogramIntervalByDatatableColumn(xColumn); + if (typeof histogramInterval === 'number') { + return histogramInterval; + } else { + return undefined; + } + } + const dateMetaData = await getIntervalByColumn(xColumn); if (!dateMetaData) return; const intervalDuration = search.aggs.parseInterval(dateMetaData.interval); if (!intervalDuration) return; @@ -381,13 +388,11 @@ export function XYChart({ const isTimeViz = data.dateRange && filteredLayers.every((l) => l.xScaleType === 'time'); const isHistogramViz = filteredLayers.every((l) => l.isHistogram); - const xDomain = isTimeViz - ? { - min: data.dateRange?.fromDate.getTime(), - max: data.dateRange?.toDate.getTime(), - minInterval, - } - : undefined; + const xDomain = { + min: isTimeViz ? data.dateRange?.fromDate.getTime() : undefined, + max: isTimeViz ? data.dateRange?.toDate.getTime() : undefined, + minInterval, + }; const getYAxesTitles = ( axisSeries: Array<{ layer: string; accessor: string }>, From 5bfcc096a6b6f24fd42eea321636181efbb4fe64 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Fri, 19 Feb 2021 07:40:16 -0500 Subject: [PATCH 71/93] [Fleet] Don't error on missing package_assets value (#91744) ## Summary closes https://github.com/elastic/kibana/issues/89111 * Update TS type to make `package_assets` key in EPM packages saved object optional * Update two places in code to deal with optional vs required property ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios #### Manual testing 1. checkout `7.10` branch 1. **start ES:** `nvm use; yarn kbn bootstrap; yarn es snapshot --version 7.10.3 --license=trial -E xpack.security.authc.api_key.enabled=true -E path.data=../data` 1. **start Kibana**: `yarn start --no-base-path` 1. **run** `curl -X POST -H 'kbn-xsrf: 1234' --user elastic:changeme localhost:5601/api/fleet/setup` 2. **observe** `{"is_initialized: true}` 1. checkout `7.11` branch 1. **start ES:** `nvm use; yarn kbn bootstrap; yarn es snapshot --version 7.11.1 --license=trial -E xpack.security.authc.api_key.enabled=true -E path.data=../data` 1. **start Kibana**: `yarn start --no-base-path` 1. **run** `curl -X POST -H 'kbn-xsrf: 1234' --user elastic:changeme localhost:5601/api/fleet/setup` 1. **observe** `{"is_initialized: true}` 1. checkout `master` branch 1. **start ES:** `nvm use; yarn kbn bootstrap; yarn es snapshot --version 8.0.0 --license=trial -E xpack.security.authc.api_key.enabled=true -E path.data=../data` 1. **start Kibana**: `yarn start --no-base-path` 1. **run** `curl -X POST -H 'kbn-xsrf: 1234' --user elastic:changeme localhost:5601/api/fleet/setup` 1. **observe error** {"statusCode":500,"error":"Internal Server Error","message":"Cannot read property 'map' of undefined"} 1. checkout this PR `8911-fleet-startup-error` 1. **start ES:** `nvm use; yarn kbn bootstrap; yarn es snapshot --version 8.0.0 --license=trial -E xpack.security.authc.api_key.enabled=true -E path.data=../data` 1. **start Kibana**: `yarn start --no-base-path` 1. **run** `curl -X POST -H 'kbn-xsrf: 1234' --user elastic:changeme localhost:5601/api/fleet/setup` 1. **observe success** `{"is_initialized: true}` **_Notes_** * _you might need to do a `yarn kbn clean` when starting kibana if it fails. There have been some big changes in the tooling recently_ --- x-pack/plugins/fleet/common/types/models/epm.ts | 2 +- .../plugins/fleet/server/services/epm/archive/storage.ts | 3 ++- x-pack/plugins/fleet/server/services/epm/packages/get.ts | 8 ++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index e7e5a931b7429f..5c99831eaac34a 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -273,7 +273,7 @@ export type PackageInfo = export interface Installation extends SavedObjectAttributes { installed_kibana: KibanaAssetReference[]; installed_es: EsAssetReference[]; - package_assets: PackageAssetReference[]; + package_assets?: PackageAssetReference[]; es_index_patterns: Record; name: string; version: string; diff --git a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts index 4144146896628f..20e1e8825fbd8f 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts @@ -83,9 +83,10 @@ export async function archiveEntryToESDocument(opts: { export async function removeArchiveEntries(opts: { savedObjectsClient: SavedObjectsClientContract; - refs: PackageAssetReference[]; + refs?: PackageAssetReference[]; }) { const { savedObjectsClient, refs } = opts; + if (!refs) return; const results = await Promise.all( refs.map((ref) => savedObjectsClient.delete(ASSETS_SAVED_OBJECT_TYPE, ref.id)) ); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 0fac68426b73e0..c07b88a45e6dc9 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -16,6 +16,7 @@ import { import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; import { ArchivePackage, RegistryPackage, EpmPackageAdditions } from '../../../../common/types'; import { Installation, PackageInfo, KibanaAssetType } from '../../../types'; +import { IngestManagerError } from '../../../errors'; import * as Registry from '../registry'; import { createInstallableFrom, isRequiredPackage } from './index'; import { getEsPackage } from '../archive/storage'; @@ -185,7 +186,8 @@ export async function getPackageFromSource(options: { name: pkgName, version: pkgVersion, }); - if (!res) { + + if (!res && installedPkg.package_assets) { res = await getEsPackage( pkgName, pkgVersion, @@ -207,7 +209,9 @@ export async function getPackageFromSource(options: { // else package is not installed or installed and missing from cache and storage and installed from registry res = await Registry.getRegistryPackage(pkgName, pkgVersion); } - if (!res) throw new Error(`package info for ${pkgName}-${pkgVersion} does not exist`); + if (!res) { + throw new IngestManagerError(`package info for ${pkgName}-${pkgVersion} does not exist`); + } return { paths: res.paths, packageInfo: res.packageInfo, From f8fd08fbcd3b75f7b34c13eb1b954523fe2a2abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Fri, 19 Feb 2021 14:34:52 +0100 Subject: [PATCH 72/93] Refactored component edit policy tests into separate folders and using client integration testing setup (#91657) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/policy_table.test.tsx.snap | 0 .../edit_policy/constants.ts | 31 + .../edit_policy/edit_policy.helpers.tsx | 183 ++-- .../edit_policy/edit_policy.test.ts | 22 +- .../cold_phase_validation.test.ts | 125 +++ .../delete_phase_validation.ts | 81 ++ .../hot_phase_validation.test.ts | 174 ++++ .../policy_name_validation.test.ts | 100 ++ .../warm_phase_validation.test.ts | 171 ++++ .../reactive_form/node_allocation.test.ts | 382 +++++++ .../reactive_form/reactive_form.test.ts | 143 +++ .../helpers/http_requests.ts | 15 +- .../__jest__/components/README.md | 8 - .../__jest__/components/edit_policy.test.tsx | 967 ------------------ .../components/helpers/edit_policy.ts | 31 - .../components/helpers/http_requests.ts | 60 -- .../__jest__/components/helpers/index.ts | 12 - .../{components => }/policy_table.test.tsx | 14 +- .../common/types/api.ts | 10 + 19 files changed, 1370 insertions(+), 1159 deletions(-) rename x-pack/plugins/index_lifecycle_management/__jest__/{components => }/__snapshots__/policy_table.test.tsx.snap (100%) create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/cold_phase_validation.test.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/delete_phase_validation.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/hot_phase_validation.test.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/policy_name_validation.test.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/warm_phase_validation.test.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/reactive_form/node_allocation.test.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/reactive_form/reactive_form.test.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/components/README.md delete mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx delete mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/edit_policy.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/http_requests.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/index.ts rename x-pack/plugins/index_lifecycle_management/__jest__/{components => }/policy_table.test.tsx (92%) diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.tsx.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap similarity index 100% rename from x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.tsx.snap rename to x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts index a63203656dc46d..2c8fbfc749a82d 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts @@ -5,6 +5,8 @@ * 2.0. */ +import moment from 'moment-timezone'; + import { PolicyFromES } from '../../../common/types'; export const POLICY_NAME = 'my_policy'; @@ -234,3 +236,32 @@ export const POLICY_WITH_KNOWN_AND_UNKNOWN_FIELDS = ({ }, name: POLICY_NAME, } as any) as PolicyFromES; + +export const getGeneratedPolicies = (): PolicyFromES[] => { + const policy = { + phases: { + hot: { + min_age: '0s', + actions: { + rollover: { + max_size: '1gb', + }, + }, + }, + }, + }; + const policies: PolicyFromES[] = []; + for (let i = 0; i < 105; i++) { + policies.push({ + version: i, + modified_date: moment().subtract(i, 'days').toISOString(), + linkedIndices: i % 2 === 0 ? [`index${i}`] : undefined, + name: `testy${i}`, + policy: { + ...policy, + name: `testy${i}`, + }, + }); + } + return policies; +}; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx index 83a13f0523a403..a9845c23156049 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx @@ -21,10 +21,12 @@ import { KibanaContextProvider } from '../../../public/shared_imports'; import { AppServicesContext } from '../../../public/types'; import { createBreadcrumbsMock } from '../../../public/application/services/breadcrumbs.mock'; +import { TestSubjects } from '../helpers'; +import { POLICY_NAME } from './constants'; + type Phases = keyof PolicyPhases; -import { POLICY_NAME } from './constants'; -import { TestSubjects } from '../helpers'; +window.scrollTo = jest.fn(); jest.mock('@elastic/eui', () => { const original = jest.requireActual('@elastic/eui'); @@ -46,14 +48,17 @@ jest.mock('@elastic/eui', () => { }; }); -const testBedConfig: TestBedConfig = { - memoryRouter: { - initialEntries: [`/policies/edit/${POLICY_NAME}`], - componentRoutePath: `/policies/edit/:policyName`, - }, - defaultProps: { - getUrlForApp: () => {}, - }, +const getTestBedConfig = (testBedConfigArgs?: Partial): TestBedConfig => { + return { + memoryRouter: { + initialEntries: [`/policies/edit/${POLICY_NAME}`], + componentRoutePath: `/policies/edit/:policyName`, + }, + defaultProps: { + getUrlForApp: () => {}, + }, + ...testBedConfigArgs, + }; }; const breadcrumbService = createBreadcrumbsMock(); @@ -72,13 +77,22 @@ const MyComponent = ({ appServicesContext, ...rest }: any) => { ); }; -const initTestBed = registerTestBed(MyComponent, testBedConfig); +const initTestBed = (arg?: { + appServicesContext?: Partial; + testBedConfig?: Partial; +}) => { + const { testBedConfig: testBedConfigArgs, ...rest } = arg || {}; + return registerTestBed(MyComponent, getTestBedConfig(testBedConfigArgs))(rest); +}; type SetupReturn = ReturnType; export type EditPolicyTestBed = SetupReturn extends Promise ? U : SetupReturn; -export const setup = async (arg?: { appServicesContext: Partial }) => { +export const setup = async (arg?: { + appServicesContext?: Partial; + testBedConfig?: Partial; +}) => { const testBed = await initTestBed(arg); const { find, component, form, exists } = testBed; @@ -169,34 +183,15 @@ export const setup = async (arg?: { appServicesContext: Partial createFormToggleAction(`enablePhaseSwitch-${phase}`); - const setMinAgeValue = (phase: Phases) => createFormSetValueAction(`${phase}-selectedMinimumAge`); - - const setMinAgeUnits = (phase: Phases) => - createFormSetValueAction(`${phase}-selectedMinimumAgeUnits`); - - const setDataAllocation = (phase: Phases) => async (value: DataTierAllocationType) => { - act(() => { - find(`${phase}-dataTierAllocationControls.dataTierSelect`).simulate('click'); - }); - component.update(); - await act(async () => { - switch (value) { - case 'node_roles': - find(`${phase}-dataTierAllocationControls.defaultDataAllocationOption`).simulate('click'); - break; - case 'node_attrs': - find(`${phase}-dataTierAllocationControls.customDataAllocationOption`).simulate('click'); - break; - default: - find(`${phase}-dataTierAllocationControls.noneDataAllocationOption`).simulate('click'); - } - }); - component.update(); + const createMinAgeActions = (phase: Phases) => { + return { + hasMinAgeInput: () => exists(`${phase}-selectedMinimumAge`), + setMinAgeValue: createFormSetValueAction(`${phase}-selectedMinimumAge`), + setMinAgeUnits: createFormSetValueAction(`${phase}-selectedMinimumAgeUnits`), + hasRolloverTipOnMinAge: () => exists(`${phase}-rolloverMinAgeInputIconTip`), + }; }; - const setSelectedNodeAttribute = (phase: Phases) => - createFormSetValueAction(`${phase}-selectedNodeAttrs`); - const setReplicas = (phase: Phases) => async (value: string) => { if (!exists(`${phase}-selectedReplicaCount`)) { await createFormToggleAction(`${phase}-setReplicasSwitch`)(true); @@ -216,8 +211,12 @@ export const setup = async (arg?: { appServicesContext: Partial exists('freezeSwitch'); - const setReadonly = (phase: Phases) => async (value: boolean) => { - await createFormToggleAction(`${phase}-readonlySwitch`)(value); + const createReadonlyActions = (phase: Phases) => { + const toggleSelector = `${phase}-readonlySwitch`; + return { + readonlyExists: () => exists(toggleSelector), + toggleReadonly: createFormToggleAction(toggleSelector), + }; }; const createSearchableSnapshotActions = (phase: Phases) => { @@ -271,17 +270,93 @@ export const setup = async (arg?: { appServicesContext: Partial (): boolean => - exists(`${phase}-rolloverMinAgeInputIconTip`); + const hasRolloverSettingRequiredCallout = (): boolean => exists('rolloverSettingsRequired'); + + const createNodeAllocationActions = (phase: Phases) => { + const controlsSelector = `${phase}-dataTierAllocationControls`; + const dataTierSelector = `${controlsSelector}.dataTierSelect`; + const nodeAttrsSelector = `${phase}-selectedNodeAttrs`; + + return { + hasDataTierAllocationControls: () => exists(controlsSelector), + openNodeAttributesSection: async () => { + await act(async () => { + find(dataTierSelector).simulate('click'); + }); + component.update(); + }, + hasNodeAttributesSelect: (): boolean => exists(nodeAttrsSelector), + getNodeAttributesSelectOptions: () => find(nodeAttrsSelector).find('option'), + setDataAllocation: async (value: DataTierAllocationType) => { + act(() => { + find(dataTierSelector).simulate('click'); + }); + component.update(); + await act(async () => { + switch (value) { + case 'node_roles': + find(`${controlsSelector}.defaultDataAllocationOption`).simulate('click'); + break; + case 'node_attrs': + find(`${controlsSelector}.customDataAllocationOption`).simulate('click'); + break; + default: + find(`${controlsSelector}.noneDataAllocationOption`).simulate('click'); + } + }); + component.update(); + }, + setSelectedNodeAttribute: createFormSetValueAction(nodeAttrsSelector), + hasNoNodeAttrsWarning: () => exists('noNodeAttributesWarning'), + hasDefaultAllocationWarning: () => exists('defaultAllocationWarning'), + hasDefaultAllocationNotice: () => exists('defaultAllocationNotice'), + hasNodeDetailsFlyout: () => exists(`${phase}-viewNodeDetailsFlyoutButton`), + openNodeDetailsFlyout: async () => { + await act(async () => { + find(`${phase}-viewNodeDetailsFlyoutButton`).simulate('click'); + }); + component.update(); + }, + }; + }; + + const expectErrorMessages = (expectedMessages: string[]) => { + const errorMessages = component.find('.euiFormErrorText'); + expect(errorMessages.length).toBe(expectedMessages.length); + expectedMessages.forEach((expectedErrorMessage) => { + let foundErrorMessage; + for (let i = 0; i < errorMessages.length; i++) { + if (errorMessages.at(i).text() === expectedErrorMessage) { + foundErrorMessage = true; + } + } + expect(foundErrorMessage).toBe(true); + }); + }; + + /* + * For new we rely on a setTimeout to ensure that error messages have time to populate + * the form object before we look at the form object. See: + * x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx + * for where this logic lives. + */ + const runTimers = () => { + act(() => { + jest.runAllTimers(); + }); + component.update(); + }; return { ...testBed, + runTimers, actions: { saveAsNewPolicy: createFormToggleAction('saveAsNewSwitch'), setPolicyName: createFormSetValueAction('policyNameField'), setWaitForSnapshotPolicy, savePolicy, hasGlobalErrorCallout: () => exists('policyFormErrorsCallout'), + expectErrorMessages, timeline: { hasHotPhase: () => exists('ilmTimelineHotPhase'), hasWarmPhase: () => exists('ilmTimelineWarmPhase'), @@ -294,46 +369,40 @@ export const setup = async (arg?: { appServicesContext: Partial exists('phaseErrorIndicator-hot'), ...createForceMergeActions('hot'), ...createIndexPriorityActions('hot'), ...createShrinkActions('hot'), - setReadonly: setReadonly('hot'), + ...createReadonlyActions('hot'), ...createSearchableSnapshotActions('hot'), }, warm: { enable: enable('warm'), - setMinAgeValue: setMinAgeValue('warm'), - setMinAgeUnits: setMinAgeUnits('warm'), - setDataAllocation: setDataAllocation('warm'), - setSelectedNodeAttribute: setSelectedNodeAttribute('warm'), + ...createMinAgeActions('warm'), setReplicas: setReplicas('warm'), hasErrorIndicator: () => exists('phaseErrorIndicator-warm'), - hasRolloverTipOnMinAge: hasRolloverTipOnMinAge('warm'), ...createShrinkActions('warm'), ...createForceMergeActions('warm'), - setReadonly: setReadonly('warm'), + ...createReadonlyActions('warm'), ...createIndexPriorityActions('warm'), + ...createNodeAllocationActions('warm'), }, cold: { enable: enable('cold'), - setMinAgeValue: setMinAgeValue('cold'), - setMinAgeUnits: setMinAgeUnits('cold'), - setDataAllocation: setDataAllocation('cold'), - setSelectedNodeAttribute: setSelectedNodeAttribute('cold'), + ...createMinAgeActions('cold'), setReplicas: setReplicas('cold'), setFreeze, freezeExists, hasErrorIndicator: () => exists('phaseErrorIndicator-cold'), - hasRolloverTipOnMinAge: hasRolloverTipOnMinAge('cold'), ...createIndexPriorityActions('cold'), ...createSearchableSnapshotActions('cold'), + ...createNodeAllocationActions('cold'), }, delete: { + isShown: () => exists('delete-phaseContent'), ...createToggleDeletePhaseActions(), - hasRolloverTipOnMinAge: hasRolloverTipOnMinAge('delete'), - setMinAgeValue: setMinAgeValue('delete'), - setMinAgeUnits: setMinAgeUnits('delete'), + ...createMinAgeActions('delete'), }, }, }; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index 859b4adce50285..7fe5c6f50d046b 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -25,8 +25,6 @@ import { getDefaultHotPhasePolicy, } from './constants'; -window.scrollTo = jest.fn(); - describe('', () => { let testBed: EditPolicyTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -127,7 +125,7 @@ describe('', () => { await actions.hot.setBestCompression(true); await actions.hot.toggleShrink(true); await actions.hot.setShrink('2'); - await actions.hot.setReadonly(true); + await actions.hot.toggleReadonly(true); await actions.hot.toggleIndexPriority(true); await actions.hot.setIndexPriority('123'); @@ -271,7 +269,7 @@ describe('', () => { await actions.warm.toggleForceMerge(true); await actions.warm.setForcemergeSegmentsCount('123'); await actions.warm.setBestCompression(true); - await actions.warm.setReadonly(true); + await actions.warm.toggleReadonly(true); await actions.warm.setIndexPriority('123'); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; @@ -918,6 +916,7 @@ describe('', () => { }); describe('policy error notifications', () => { + let runTimers: () => void; beforeAll(() => { jest.useFakeTimers(); }); @@ -925,6 +924,7 @@ describe('', () => { afterAll(() => { jest.useRealTimers(); }); + beforeEach(async () => { httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy('my_policy')]); httpRequestsMockHelpers.setListNodes({ @@ -940,19 +940,9 @@ describe('', () => { const { component } = testBed; component.update(); - }); - // For new we rely on a setTimeout to ensure that error messages have time to populate - // the form object before we look at the form object. See: - // x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx - // for where this logic lives. - const runTimers = () => { - const { component } = testBed; - act(() => { - jest.runAllTimers(); - }); - component.update(); - }; + ({ runTimers } = testBed); + }); test('shows phase error indicators correctly', async () => { // This test simulates a user configuring a policy phase by phase. The flow is the following: diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/cold_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/cold_phase_validation.test.ts new file mode 100644 index 00000000000000..c5c4bb1be87e0e --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/cold_phase_validation.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; +import { setupEnvironment } from '../../helpers/setup_environment'; +import { EditPolicyTestBed, setup } from '../edit_policy.helpers'; + +describe(' cold phase validation', () => { + let testBed: EditPolicyTestBed; + let runTimers: () => void; + const { server, httpRequestsMockHelpers } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + server.restore(); + }); + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: { data: ['node1'] }, + nodesByAttributes: { 'attribute:true': ['node1'] }, + isUsingDeprecatedDataRoleConfig: true, + }); + httpRequestsMockHelpers.setNodesDetails('attribute:true', [ + { nodeId: 'testNodeId', stats: { name: 'testNodeName', host: 'testHost' } }, + ]); + + await act(async () => { + testBed = await setup(); + }); + + const { component, actions } = testBed; + component.update(); + await actions.setPolicyName('mypolicy'); + await actions.cold.enable(true); + + ({ runTimers } = testBed); + }); + + describe('timing', () => { + test(`doesn't allow empty timing`, async () => { + const { actions } = testBed; + + await actions.cold.setMinAgeValue(''); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); + }); + + test(`allows 0 for phase timing`, async () => { + const { actions } = testBed; + + await actions.cold.setMinAgeValue('0'); + + runTimers(); + + actions.expectErrorMessages([]); + }); + + test(`doesn't allow -1 for timing`, async () => { + const { actions } = testBed; + + await actions.cold.setMinAgeValue('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); + }); + }); + + describe('replicas', () => { + test(`doesn't allow -1 for replicas`, async () => { + const { actions } = testBed; + + await actions.cold.setReplicas('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); + }); + + test(`allows 0 for replicas`, async () => { + const { actions } = testBed; + + await actions.cold.setReplicas('0'); + + runTimers(); + + actions.expectErrorMessages([]); + }); + }); + + describe('index priority', () => { + test(`doesn't allow -1 for index priority`, async () => { + const { actions } = testBed; + + await actions.cold.setIndexPriority('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); + }); + + test(`allows 0 for index priority`, async () => { + const { actions } = testBed; + + await actions.cold.setIndexPriority('0'); + + runTimers(); + + actions.expectErrorMessages([]); + }); + }); +}); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/delete_phase_validation.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/delete_phase_validation.ts new file mode 100644 index 00000000000000..a13aaa02dcd068 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/delete_phase_validation.ts @@ -0,0 +1,81 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; +import { setupEnvironment } from '../../helpers/setup_environment'; +import { EditPolicyTestBed, setup } from '../edit_policy.helpers'; + +describe(' delete phase validation', () => { + let testBed: EditPolicyTestBed; + let runTimers: () => void; + const { server, httpRequestsMockHelpers } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + server.restore(); + }); + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: { data: ['node1'] }, + nodesByAttributes: { 'attribute:true': ['node1'] }, + isUsingDeprecatedDataRoleConfig: true, + }); + httpRequestsMockHelpers.setNodesDetails('attribute:true', [ + { nodeId: 'testNodeId', stats: { name: 'testNodeName', host: 'testHost' } }, + ]); + + await act(async () => { + testBed = await setup(); + }); + + const { component, actions } = testBed; + component.update(); + await actions.setPolicyName('mypolicy'); + await actions.delete.enablePhase(); + + ({ runTimers } = testBed); + }); + + describe('timing', () => { + test(`doesn't allow empty timing`, async () => { + const { actions } = testBed; + + await actions.delete.setMinAgeValue(''); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); + }); + + test(`allows 0 for phase timing`, async () => { + const { actions } = testBed; + + await actions.delete.setMinAgeValue('0'); + + runTimers(); + + actions.expectErrorMessages([]); + }); + + test(`doesn't allow -1 for timing`, async () => { + const { actions } = testBed; + + await actions.delete.setMinAgeValue('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); + }); + }); +}); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/hot_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/hot_phase_validation.test.ts new file mode 100644 index 00000000000000..7c1d687b27e3d1 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/hot_phase_validation.test.ts @@ -0,0 +1,174 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; +import { setupEnvironment } from '../../helpers/setup_environment'; +import { EditPolicyTestBed, setup } from '../edit_policy.helpers'; + +describe(' hot phase validation', () => { + let testBed: EditPolicyTestBed; + let runTimers: () => void; + const { server, httpRequestsMockHelpers } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + server.restore(); + }); + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([]); + await act(async () => { + testBed = await setup(); + }); + + const { component, actions } = testBed; + component.update(); + await actions.setPolicyName('mypolicy'); + + ({ runTimers } = testBed); + }); + + describe('rollover', () => { + test(`doesn't allow no max size, no max age and no max docs`, async () => { + const { actions } = testBed; + + await actions.hot.toggleDefaultRollover(false); + expect(actions.hot.hasRolloverSettingRequiredCallout()).toBeFalsy(); + + await actions.hot.setMaxSize(''); + await actions.hot.setMaxAge(''); + await actions.hot.setMaxDocs(''); + + runTimers(); + + expect(actions.hot.hasRolloverSettingRequiredCallout()).toBeTruthy(); + }); + + test(`doesn't allow -1 for max size`, async () => { + const { actions } = testBed; + + await actions.hot.toggleDefaultRollover(false); + await actions.hot.setMaxSize('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + + test(`doesn't allow 0 for max size`, async () => { + const { actions } = testBed; + + await actions.hot.toggleDefaultRollover(false); + await actions.hot.setMaxSize('0'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + + test(`doesn't allow -1 for max age`, async () => { + const { actions } = testBed; + + await actions.hot.toggleDefaultRollover(false); + await actions.hot.setMaxAge('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + + test(`doesn't allow 0 for max age`, async () => { + const { actions } = testBed; + + await actions.hot.toggleDefaultRollover(false); + await actions.hot.setMaxAge('0'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + + test(`doesn't allow -1 for max docs`, async () => { + const { actions } = testBed; + + await actions.hot.toggleDefaultRollover(false); + await actions.hot.setMaxDocs('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + + test(`doesn't allow 0 for max docs`, async () => { + const { actions } = testBed; + + await actions.hot.toggleDefaultRollover(false); + await actions.hot.setMaxDocs('0'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + }); + + describe('forcemerge', () => { + test(`doesn't allow 0 for forcemerge`, async () => { + const { actions } = testBed; + await actions.hot.toggleForceMerge(true); + await actions.hot.setForcemergeSegmentsCount('0'); + runTimers(); + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + test(`doesn't allow -1 for forcemerge`, async () => { + const { actions } = testBed; + await actions.hot.toggleForceMerge(true); + await actions.hot.setForcemergeSegmentsCount('-1'); + runTimers(); + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + }); + + describe('shrink', () => { + test(`doesn't allow 0 for shrink`, async () => { + const { actions } = testBed; + await actions.hot.toggleShrink(true); + await actions.hot.setShrink('0'); + runTimers(); + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + test(`doesn't allow -1 for shrink`, async () => { + const { actions } = testBed; + await actions.hot.toggleShrink(true); + await actions.hot.setShrink('-1'); + runTimers(); + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + }); + + describe('index priority', () => { + test(`doesn't allow -1 for index priority`, async () => { + const { actions } = testBed; + + await actions.hot.setIndexPriority('-1'); + runTimers(); + actions.expectErrorMessages([i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); + }); + + test(`allows 0 for index priority`, async () => { + const { actions } = testBed; + + await actions.hot.setIndexPriority('0'); + runTimers(); + actions.expectErrorMessages([]); + }); + }); +}); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/policy_name_validation.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/policy_name_validation.test.ts new file mode 100644 index 00000000000000..0acb425b1d9758 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/policy_name_validation.test.ts @@ -0,0 +1,100 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; +import { setupEnvironment } from '../../helpers/setup_environment'; +import { EditPolicyTestBed, setup } from '../edit_policy.helpers'; +import { getGeneratedPolicies } from '../constants'; + +describe(' policy name validation', () => { + let testBed: EditPolicyTestBed; + let runTimers: () => void; + const { server, httpRequestsMockHelpers } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + server.restore(); + }); + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies(getGeneratedPolicies()); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + + ({ runTimers } = testBed); + }); + + test(`doesn't allow empty policy name`, async () => { + const { actions } = testBed; + await actions.savePolicy(); + actions.expectErrorMessages([i18nTexts.editPolicy.errors.policyNameRequiredMessage]); + }); + + test(`doesn't allow policy name with space`, async () => { + const { actions } = testBed; + await actions.setPolicyName('my policy'); + runTimers(); + actions.expectErrorMessages([i18nTexts.editPolicy.errors.policyNameContainsInvalidChars]); + }); + + test(`doesn't allow policy name that is already used`, async () => { + const { actions } = testBed; + await actions.setPolicyName('testy0'); + runTimers(); + actions.expectErrorMessages([i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage]); + }); + + test(`doesn't allow to save as new policy but using the same name`, async () => { + await act(async () => { + testBed = await setup({ + testBedConfig: { + memoryRouter: { + initialEntries: [`/policies/edit/testy0`], + componentRoutePath: `/policies/edit/:policyName`, + }, + }, + }); + }); + const { component, actions } = testBed; + component.update(); + + ({ runTimers } = testBed); + + await actions.saveAsNewPolicy(true); + runTimers(); + await actions.savePolicy(); + actions.expectErrorMessages([ + i18nTexts.editPolicy.errors.policyNameMustBeDifferentErrorMessage, + ]); + }); + + test(`doesn't allow policy name with comma`, async () => { + const { actions } = testBed; + await actions.setPolicyName('my,policy'); + runTimers(); + actions.expectErrorMessages([i18nTexts.editPolicy.errors.policyNameContainsInvalidChars]); + }); + + test(`doesn't allow policy name starting with underscore`, async () => { + const { actions } = testBed; + await actions.setPolicyName('_mypolicy'); + runTimers(); + actions.expectErrorMessages([ + i18nTexts.editPolicy.errors.policyNameStartsWithUnderscoreErrorMessage, + ]); + }); +}); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/warm_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/warm_phase_validation.test.ts new file mode 100644 index 00000000000000..2121dba8e06f63 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/warm_phase_validation.test.ts @@ -0,0 +1,171 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { i18nTexts } from '../../../../public/application/sections/edit_policy/i18n_texts'; +import { setupEnvironment } from '../../helpers/setup_environment'; +import { EditPolicyTestBed, setup } from '../edit_policy.helpers'; + +describe(' warm phase validation', () => { + let testBed: EditPolicyTestBed; + let runTimers: () => void; + const { server, httpRequestsMockHelpers } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + server.restore(); + }); + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: { data: ['node1'] }, + nodesByAttributes: { 'attribute:true': ['node1'] }, + isUsingDeprecatedDataRoleConfig: true, + }); + httpRequestsMockHelpers.setNodesDetails('attribute:true', [ + { nodeId: 'testNodeId', stats: { name: 'testNodeName', host: 'testHost' } }, + ]); + + await act(async () => { + testBed = await setup(); + }); + + const { component, actions } = testBed; + component.update(); + await actions.setPolicyName('mypolicy'); + await actions.warm.enable(true); + + ({ runTimers } = testBed); + }); + + describe('timing', () => { + test(`doesn't allow empty timing`, async () => { + const { actions } = testBed; + + await actions.warm.setMinAgeValue(''); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); + }); + + test(`allows 0 for phase timing`, async () => { + const { actions } = testBed; + + await actions.warm.setMinAgeValue('0'); + + runTimers(); + + actions.expectErrorMessages([]); + }); + + test(`doesn't allow -1 for timing`, async () => { + const { actions } = testBed; + + await actions.warm.setMinAgeValue('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); + }); + }); + + describe('replicas', () => { + test(`doesn't allow -1 for replicas`, async () => { + const { actions } = testBed; + + await actions.warm.setReplicas('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); + }); + + test(`allows 0 for replicas`, async () => { + const { actions } = testBed; + + await actions.warm.setReplicas('0'); + + runTimers(); + + actions.expectErrorMessages([]); + }); + }); + + describe('shrink', () => { + test(`doesn't allow 0 for shrink`, async () => { + const { actions } = testBed; + + await actions.warm.toggleShrink(true); + await actions.warm.setShrink('0'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + test(`doesn't allow -1 for shrink`, async () => { + const { actions } = testBed; + + await actions.warm.toggleShrink(true); + await actions.warm.setShrink('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + }); + + describe('forcemerge', () => { + test(`doesn't allow 0 for forcemerge`, async () => { + const { actions } = testBed; + + await actions.warm.toggleForceMerge(true); + await actions.warm.setForcemergeSegmentsCount('0'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + test(`doesn't allow -1 for forcemerge`, async () => { + const { actions } = testBed; + + await actions.warm.toggleForceMerge(true); + await actions.warm.setForcemergeSegmentsCount('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.numberGreatThan0Required]); + }); + }); + + describe('index priority', () => { + test(`doesn't allow -1 for index priority`, async () => { + const { actions } = testBed; + + await actions.warm.setIndexPriority('-1'); + + runTimers(); + + actions.expectErrorMessages([i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); + }); + + test(`allows 0 for index priority`, async () => { + const { actions } = testBed; + + await actions.warm.setIndexPriority('0'); + + runTimers(); + + actions.expectErrorMessages([]); + }); + }); +}); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/reactive_form/node_allocation.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/reactive_form/node_allocation.test.ts new file mode 100644 index 00000000000000..113698fdf6df2b --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/reactive_form/node_allocation.test.ts @@ -0,0 +1,382 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { setupEnvironment } from '../../helpers/setup_environment'; +import { EditPolicyTestBed, setup } from '../edit_policy.helpers'; + +describe(' node allocation', () => { + let testBed: EditPolicyTestBed; + const { server, httpRequestsMockHelpers } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + server.restore(); + }); + + beforeEach(async () => { + server.respondImmediately = true; + httpRequestsMockHelpers.setLoadPolicies([]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: { data: ['node1'] }, + nodesByAttributes: { 'attribute:true': ['node1'] }, + isUsingDeprecatedDataRoleConfig: true, + }); + httpRequestsMockHelpers.setNodesDetails('attribute:true', [ + { nodeId: 'testNodeId', stats: { name: 'testNodeName', host: 'testHost' } }, + ]); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + }); + + describe('warm phase', () => { + test('shows spinner for node attributes input when loading', async () => { + server.respondImmediately = false; + + const { actions, component } = testBed; + await actions.warm.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeTruthy(); + expect(actions.warm.hasDataTierAllocationControls()).toBeTruthy(); + + expect(component.find('.euiCallOut--warning').exists()).toBeFalsy(); + expect(actions.warm.hasNodeAttributesSelect()).toBeFalsy(); + }); + + test('shows warning instead of node attributes input when none exist', async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: {}, + nodesByRoles: { data: ['node1'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + + await act(async () => { + testBed = await setup(); + }); + const { actions, component } = testBed; + + component.update(); + await actions.warm.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + + await actions.warm.setDataAllocation('node_attrs'); + expect(actions.warm.hasNoNodeAttrsWarning()).toBeTruthy(); + expect(actions.warm.hasNodeAttributesSelect()).toBeFalsy(); + }); + + test('shows node attributes input when attributes exist', async () => { + const { actions, component } = testBed; + await actions.warm.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + await actions.warm.setDataAllocation('node_attrs'); + expect(actions.warm.hasNoNodeAttrsWarning()).toBeFalsy(); + expect(actions.warm.hasNodeAttributesSelect()).toBeTruthy(); + expect(actions.warm.getNodeAttributesSelectOptions().length).toBe(2); + }); + + test('shows view node attributes link when attribute selected and shows flyout when clicked', async () => { + const { actions, component } = testBed; + await actions.warm.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + await actions.warm.setDataAllocation('node_attrs'); + expect(actions.warm.hasNoNodeAttrsWarning()).toBeFalsy(); + expect(actions.warm.hasNodeAttributesSelect()).toBeTruthy(); + + expect(actions.warm.hasNodeDetailsFlyout()).toBeFalsy(); + expect(actions.warm.getNodeAttributesSelectOptions().length).toBe(2); + await actions.warm.setSelectedNodeAttribute('attribute:true'); + + await actions.warm.openNodeDetailsFlyout(); + expect(actions.warm.hasNodeDetailsFlyout()).toBeTruthy(); + }); + + test('shows default allocation warning when no node roles are found', async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: {}, + nodesByRoles: {}, + isUsingDeprecatedDataRoleConfig: false, + }); + + await act(async () => { + testBed = await setup(); + }); + const { actions, component } = testBed; + + component.update(); + await actions.warm.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + expect(actions.warm.hasDefaultAllocationWarning()).toBeTruthy(); + }); + + test('shows default allocation notice when hot tier exists, but not warm tier', async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: {}, + nodesByRoles: { data_hot: ['test'], data_cold: ['test'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + + await act(async () => { + testBed = await setup(); + }); + const { actions, component } = testBed; + + component.update(); + await actions.warm.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + expect(actions.warm.hasDefaultAllocationNotice()).toBeTruthy(); + }); + + test(`doesn't show default allocation notice when node with "data" role exists`, async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: {}, + nodesByRoles: { data: ['test'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + await act(async () => { + testBed = await setup(); + }); + const { actions, component } = testBed; + + component.update(); + await actions.warm.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + expect(actions.warm.hasDefaultAllocationNotice()).toBeFalsy(); + }); + }); + + describe('cold phase', () => { + test('shows spinner for node attributes input when loading', async () => { + server.respondImmediately = false; + + const { actions, component } = testBed; + await actions.cold.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeTruthy(); + expect(actions.cold.hasDataTierAllocationControls()).toBeTruthy(); + + expect(component.find('.euiCallOut--warning').exists()).toBeFalsy(); + expect(actions.cold.hasNodeAttributesSelect()).toBeFalsy(); + }); + + test('shows warning instead of node attributes input when none exist', async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: {}, + nodesByRoles: { data: ['node1'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + + await act(async () => { + testBed = await setup(); + }); + const { actions, component } = testBed; + + component.update(); + await actions.cold.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + await actions.cold.setDataAllocation('node_attrs'); + expect(actions.cold.hasNoNodeAttrsWarning()).toBeTruthy(); + expect(actions.cold.hasNodeAttributesSelect()).toBeFalsy(); + }); + + test('shows node attributes input when attributes exist', async () => { + const { actions, component } = testBed; + await actions.cold.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + await actions.cold.setDataAllocation('node_attrs'); + expect(actions.cold.hasNoNodeAttrsWarning()).toBeFalsy(); + expect(actions.cold.hasNodeAttributesSelect()).toBeTruthy(); + expect(actions.cold.getNodeAttributesSelectOptions().length).toBe(2); + }); + + test('shows view node attributes link when attribute selected and shows flyout when clicked', async () => { + const { actions, component } = testBed; + await actions.cold.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + await actions.cold.setDataAllocation('node_attrs'); + expect(actions.cold.hasNoNodeAttrsWarning()).toBeFalsy(); + expect(actions.cold.hasNodeAttributesSelect()).toBeTruthy(); + + expect(actions.cold.hasNodeDetailsFlyout()).toBeFalsy(); + expect(actions.cold.getNodeAttributesSelectOptions().length).toBe(2); + await actions.cold.setSelectedNodeAttribute('attribute:true'); + + await actions.cold.openNodeDetailsFlyout(); + expect(actions.cold.hasNodeDetailsFlyout()).toBeTruthy(); + }); + + test('shows default allocation warning when no node roles are found', async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: {}, + nodesByRoles: {}, + isUsingDeprecatedDataRoleConfig: false, + }); + + await act(async () => { + testBed = await setup(); + }); + const { actions, component } = testBed; + + component.update(); + await actions.cold.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + expect(actions.cold.hasDefaultAllocationWarning()).toBeTruthy(); + }); + + test('shows default allocation notice when warm or hot tiers exists, but not cold tier', async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: {}, + nodesByRoles: { data_hot: ['test'], data_warm: ['test'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + + await act(async () => { + testBed = await setup(); + }); + const { actions, component } = testBed; + + component.update(); + await actions.cold.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + expect(actions.cold.hasDefaultAllocationNotice()).toBeTruthy(); + }); + + test(`doesn't show default allocation notice when node with "data" role exists`, async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: {}, + nodesByRoles: { data: ['test'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + await act(async () => { + testBed = await setup(); + }); + const { actions, component } = testBed; + + component.update(); + await actions.cold.enable(true); + + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + expect(actions.cold.hasDefaultAllocationNotice()).toBeFalsy(); + }); + }); + + describe('not on cloud', () => { + test('shows all allocation options, even if using legacy config', async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: { test: ['123'] }, + nodesByRoles: { data: ['test'], data_hot: ['test'], data_warm: ['test'] }, + isUsingDeprecatedDataRoleConfig: true, + }); + await act(async () => { + testBed = await setup(); + }); + const { actions, component, exists } = testBed; + + component.update(); + await actions.warm.enable(true); + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + + // Assert that default, custom and 'none' options exist + await actions.warm.openNodeAttributesSection(); + expect(exists('defaultDataAllocationOption')).toBeTruthy(); + expect(exists('customDataAllocationOption')).toBeTruthy(); + expect(exists('noneDataAllocationOption')).toBeTruthy(); + }); + }); + + describe('on cloud', () => { + describe('with deprecated data role config', () => { + test('should hide data tier option on cloud using legacy node role configuration', async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: { test: ['123'] }, + // On cloud, if using legacy config there will not be any "data_*" roles set. + nodesByRoles: { data: ['test'] }, + isUsingDeprecatedDataRoleConfig: true, + }); + await act(async () => { + testBed = await setup({ appServicesContext: { cloud: { isCloudEnabled: true } } }); + }); + const { actions, component, exists } = testBed; + + component.update(); + await actions.warm.enable(true); + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + + // Assert that custom and 'none' options exist + await actions.warm.openNodeAttributesSection(); + expect(exists('defaultDataAllocationOption')).toBeFalsy(); + expect(exists('customDataAllocationOption')).toBeTruthy(); + expect(exists('noneDataAllocationOption')).toBeTruthy(); + }); + }); + + describe('with node role config', () => { + test('shows off, custom and data role options on cloud with data roles', async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: { test: ['123'] }, + nodesByRoles: { data: ['test'], data_hot: ['test'], data_warm: ['test'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + await act(async () => { + testBed = await setup({ appServicesContext: { cloud: { isCloudEnabled: true } } }); + }); + const { actions, component, exists } = testBed; + + component.update(); + await actions.warm.enable(true); + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + + await actions.warm.openNodeAttributesSection(); + expect(exists('defaultDataAllocationOption')).toBeTruthy(); + expect(exists('customDataAllocationOption')).toBeTruthy(); + expect(exists('noneDataAllocationOption')).toBeTruthy(); + // We should not be showing the call-to-action for users to activate data tiers in cloud + expect(exists('cloudDataTierCallout')).toBeFalsy(); + }); + + test('shows cloud notice when cold tier nodes do not exist', async () => { + httpRequestsMockHelpers.setListNodes({ + nodesByAttributes: {}, + nodesByRoles: { data: ['test'], data_hot: ['test'], data_warm: ['test'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + await act(async () => { + testBed = await setup({ appServicesContext: { cloud: { isCloudEnabled: true } } }); + }); + const { actions, component, exists } = testBed; + + component.update(); + await actions.cold.enable(true); + expect(component.find('.euiLoadingSpinner').exists()).toBeFalsy(); + + expect(exists('cloudDataTierCallout')).toBeTruthy(); + // Assert that other notices are not showing + expect(actions.cold.hasDefaultAllocationNotice()).toBeFalsy(); + expect(actions.cold.hasNoNodeAttrsWarning()).toBeFalsy(); + }); + }); + }); +}); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/reactive_form/reactive_form.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/reactive_form/reactive_form.test.ts new file mode 100644 index 00000000000000..9c23780f1d0213 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/reactive_form/reactive_form.test.ts @@ -0,0 +1,143 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { setupEnvironment } from '../../helpers/setup_environment'; +import { EditPolicyTestBed, setup } from '../edit_policy.helpers'; +import { DEFAULT_POLICY } from '../constants'; + +describe(' reactive form', () => { + let testBed: EditPolicyTestBed; + const { server, httpRequestsMockHelpers } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + server.restore(); + }); + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([DEFAULT_POLICY]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: { data: ['node1'] }, + nodesByAttributes: { 'attribute:true': ['node1'] }, + isUsingDeprecatedDataRoleConfig: true, + }); + httpRequestsMockHelpers.setNodesDetails('attribute:true', [ + { nodeId: 'testNodeId', stats: { name: 'testNodeName', host: 'testHost' } }, + ]); + httpRequestsMockHelpers.setLoadSnapshotPolicies([]); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + }); + + describe('rollover', () => { + test('shows forcemerge when rollover enabled', async () => { + const { actions } = testBed; + expect(actions.hot.forceMergeFieldExists()).toBeTruthy(); + }); + test('hides forcemerge when rollover is disabled', async () => { + const { actions } = testBed; + await actions.hot.toggleDefaultRollover(false); + await actions.hot.toggleRollover(false); + expect(actions.hot.forceMergeFieldExists()).toBeFalsy(); + }); + + test('shows shrink input when rollover enabled', async () => { + const { actions } = testBed; + expect(actions.hot.shrinkExists()).toBeTruthy(); + }); + test('hides shrink input when rollover is disabled', async () => { + const { actions } = testBed; + await actions.hot.toggleDefaultRollover(false); + await actions.hot.toggleRollover(false); + expect(actions.hot.shrinkExists()).toBeFalsy(); + }); + test('shows readonly input when rollover enabled', async () => { + const { actions } = testBed; + expect(actions.hot.readonlyExists()).toBeTruthy(); + }); + test('hides readonly input when rollover is disabled', async () => { + const { actions } = testBed; + await actions.hot.toggleDefaultRollover(false); + await actions.hot.toggleRollover(false); + expect(actions.hot.readonlyExists()).toBeFalsy(); + }); + }); + + describe('timing', () => { + test('warm phase shows timing only when enabled', async () => { + const { actions } = testBed; + expect(actions.warm.hasMinAgeInput()).toBeFalsy(); + await actions.warm.enable(true); + expect(actions.warm.hasMinAgeInput()).toBeTruthy(); + }); + + test('cold phase shows timing only when enabled', async () => { + const { actions } = testBed; + expect(actions.cold.hasMinAgeInput()).toBeFalsy(); + await actions.cold.enable(true); + expect(actions.cold.hasMinAgeInput()).toBeTruthy(); + }); + + test('delete phase shows timing after it was enabled', async () => { + const { actions } = testBed; + expect(actions.delete.hasMinAgeInput()).toBeFalsy(); + await actions.delete.enablePhase(); + expect(actions.delete.hasMinAgeInput()).toBeTruthy(); + }); + }); + + describe('delete phase', () => { + test('is hidden when disabled', async () => { + const { actions } = testBed; + expect(actions.delete.isShown()).toBeFalsy(); + await actions.delete.enablePhase(); + expect(actions.delete.isShown()).toBeTruthy(); + }); + }); + + describe('json in flyout', () => { + test('renders a json in flyout for a default policy', async () => { + const { find, component } = testBed; + await act(async () => { + find('requestButton').simulate('click'); + }); + component.update(); + + const json = component.find(`code`).text(); + const expected = `PUT _ilm/policy/my_policy\n${JSON.stringify( + { + policy: { + phases: { + hot: { + min_age: '0ms', + actions: { + rollover: { + max_age: '30d', + max_size: '50gb', + }, + }, + }, + }, + }, + }, + null, + 2 + )}`; + expect(json).toBe(expected); + }); + }); +}); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/http_requests.ts index 823138aad13b97..6ef2b4c231ce1a 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/http_requests.ts @@ -7,7 +7,11 @@ import { fakeServer, SinonFakeServer } from 'sinon'; import { API_BASE_PATH } from '../../../common/constants'; -import { ListNodesRouteResponse, ListSnapshotReposResponse } from '../../../common/types'; +import { + ListNodesRouteResponse, + ListSnapshotReposResponse, + NodesDetailsResponse, +} from '../../../common/types'; export const init = () => { const server = fakeServer.create(); @@ -48,6 +52,14 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ]); }; + const setNodesDetails = (nodeAttributes: string, body: NodesDetailsResponse) => { + server.respondWith('GET', `${API_BASE_PATH}/nodes/${nodeAttributes}/details`, [ + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify(body), + ]); + }; + const setListSnapshotRepos = (body: ListSnapshotReposResponse) => { server.respondWith('GET', `${API_BASE_PATH}/snapshot_repositories`, [ 200, @@ -60,6 +72,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { setLoadPolicies, setLoadSnapshotPolicies, setListNodes, + setNodesDetails, setListSnapshotRepos, }; }; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/README.md b/x-pack/plugins/index_lifecycle_management/__jest__/components/README.md deleted file mode 100644 index ce1ea7aa396a62..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Deprecated - -This test folder contains useful test coverage, mostly error states for form validation. However, it is -not in keeping with other ES UI maintained plugins. See ../client_integration for the established pattern -of tests. - -The tests here should be migrated to the above pattern and should not be added to. Any new test coverage must -be added to ../client_integration. diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx deleted file mode 100644 index 7c199e2ced7651..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx +++ /dev/null @@ -1,967 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { ReactElement } from 'react'; -import { act } from 'react-dom/test-utils'; -import moment from 'moment-timezone'; - -import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test/jest'; -import { SinonFakeServer } from 'sinon'; -import { ReactWrapper } from 'enzyme'; -import axios from 'axios'; -import axiosXhrAdapter from 'axios/lib/adapters/xhr'; -import { createMemoryHistory } from 'history'; - -import { - notificationServiceMock, - fatalErrorsServiceMock, -} from '../../../../../src/core/public/mocks'; - -import { usageCollectionPluginMock } from '../../../../../src/plugins/usage_collection/public/mocks'; - -import { CloudSetup } from '../../../cloud/public'; - -import { EditPolicy } from '../../public/application/sections/edit_policy/edit_policy'; -import { - EditPolicyContextProvider, - EditPolicyContextValue, -} from '../../public/application/sections/edit_policy/edit_policy_context'; - -import { KibanaContextProvider } from '../../public/shared_imports'; - -import { init as initHttp } from '../../public/application/services/http'; -import { init as initUiMetric } from '../../public/application/services/ui_metric'; -import { init as initNotification } from '../../public/application/services/notification'; -import { PolicyFromES } from '../../common/types'; - -import { i18nTexts } from '../../public/application/sections/edit_policy/i18n_texts'; -import { editPolicyHelpers } from './helpers'; -import { defaultPolicy } from '../../public/application/constants'; - -// @ts-ignore -initHttp(axios.create({ adapter: axiosXhrAdapter })); -initUiMetric(usageCollectionPluginMock.createSetupContract()); -initNotification( - notificationServiceMock.createSetupContract().toasts, - fatalErrorsServiceMock.createSetupContract() -); - -const history = createMemoryHistory(); -let server: SinonFakeServer; -let httpRequestsMockHelpers: editPolicyHelpers.EditPolicySetup['http']['httpRequestsMockHelpers']; -let http: editPolicyHelpers.EditPolicySetup['http']; -const policy = { - phases: { - hot: { - min_age: '0s', - actions: { - rollover: { - max_size: '1gb', - }, - }, - }, - }, -}; -const policies: PolicyFromES[] = []; -for (let i = 0; i < 105; i++) { - policies.push({ - version: i, - modified_date: moment().subtract(i, 'days').toISOString(), - linkedIndices: i % 2 === 0 ? [`index${i}`] : undefined, - name: `testy${i}`, - policy: { - ...policy, - name: `testy${i}`, - }, - }); -} -window.scrollTo = jest.fn(); - -jest.mock('@elastic/eui', () => { - const original = jest.requireActual('@elastic/eui'); - - return { - ...original, - EuiIcon: 'eui-icon', // using custom react-svg icon causes issues, mocking for now. - }; -}); - -let component: ReactElement; -const activatePhase = async (rendered: ReactWrapper, phase: string) => { - const testSubject = `enablePhaseSwitch-${phase}`; - await act(async () => { - await findTestSubject(rendered, testSubject).simulate('click'); - }); - rendered.update(); -}; -const activateDeletePhase = async (rendered: ReactWrapper) => { - const testSubject = `enableDeletePhaseButton`; - await act(async () => { - await findTestSubject(rendered, testSubject).simulate('click'); - }); - rendered.update(); -}; -const openNodeAttributesSection = async (rendered: ReactWrapper, phase: string) => { - const getControls = () => findTestSubject(rendered, `${phase}-dataTierAllocationControls`); - await act(async () => { - findTestSubject(getControls(), 'dataTierSelect').simulate('click'); - }); - rendered.update(); - await act(async () => { - findTestSubject(getControls(), 'customDataAllocationOption').simulate('click'); - }); - rendered.update(); -}; -const expectedErrorMessages = (rendered: ReactWrapper, expectedMessages: string[]) => { - const errorMessages = rendered.find('.euiFormErrorText'); - expect(errorMessages.length).toBe(expectedMessages.length); - expectedMessages.forEach((expectedErrorMessage) => { - let foundErrorMessage; - for (let i = 0; i < errorMessages.length; i++) { - if (errorMessages.at(i).text() === expectedErrorMessage) { - foundErrorMessage = true; - } - } - expect(foundErrorMessage).toBe(true); - }); -}; -const noDefaultRollover = async (rendered: ReactWrapper) => { - await act(async () => { - findTestSubject(rendered, 'useDefaultRolloverSwitch').simulate('click'); - }); - rendered.update(); -}; -const noRollover = async (rendered: ReactWrapper) => { - await noDefaultRollover(rendered); - await act(async () => { - findTestSubject(rendered, 'rolloverSwitch').simulate('click'); - }); - rendered.update(); -}; -const getNodeAttributeSelect = (rendered: ReactWrapper, phase: string) => { - return findTestSubject(rendered, `${phase}-selectedNodeAttrs`); -}; -const setPolicyName = async (rendered: ReactWrapper, policyName: string) => { - const policyNameField = findTestSubject(rendered, 'policyNameField'); - await act(async () => { - policyNameField.simulate('change', { target: { value: policyName } }); - }); - rendered.update(); -}; -const setPhaseAfter = async (rendered: ReactWrapper, phase: string, after: string | number) => { - const afterInput = findTestSubject(rendered, `${phase}-selectedMinimumAge`); - await act(async () => { - afterInput.simulate('change', { target: { value: after } }); - }); - rendered.update(); -}; -const setPhaseIndexPriority = async ( - rendered: ReactWrapper, - phase: string, - priority: string | number -) => { - const priorityInput = findTestSubject(rendered, `${phase}-indexPriority`); - await act(async () => { - priorityInput.simulate('change', { target: { value: priority } }); - }); - rendered.update(); -}; -const save = async (rendered: ReactWrapper) => { - const saveButton = findTestSubject(rendered, 'savePolicyButton'); - await act(async () => { - saveButton.simulate('click'); - }); - rendered.update(); -}; - -const MyComponent = ({ - isCloudEnabled, - isNewPolicy, - policy: _policy, - existingPolicies, - getUrlForApp, - policyName, -}: EditPolicyContextValue & { isCloudEnabled: boolean }) => { - return ( - - true, - }, - }} - > - - - - ); -}; - -describe('edit policy', () => { - beforeAll(() => { - jest.useFakeTimers(); - }); - afterAll(() => { - jest.useRealTimers(); - }); - - /** - * The form lib has a short delay (setTimeout) before running and rendering - * any validation errors. This helper advances timers and can trigger component - * state changes. - */ - const waitForFormLibValidation = (rendered: ReactWrapper) => { - act(() => { - jest.runAllTimers(); - }); - rendered.update(); - }; - - beforeEach(() => { - component = ( - true }} - /> - ); - - ({ http } = editPolicyHelpers.setup()); - ({ server, httpRequestsMockHelpers } = http); - - httpRequestsMockHelpers.setPoliciesResponse(policies); - }); - describe('top level form', () => { - test('should show error when trying to save empty form', async () => { - const rendered = mountWithIntl(component); - await save(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameRequiredMessage]); - }); - test('should show error when trying to save policy name with space', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'my policy'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameContainsInvalidChars]); - }); - test('should show error when trying to save policy name that is already used', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'testy0'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [ - i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage, - ]); - }); - test('should show error when trying to save as new policy but using the same name', async () => { - component = ( - true }} - /> - ); - const rendered = mountWithIntl(component); - findTestSubject(rendered, 'saveAsNewSwitch').simulate('click'); - rendered.update(); - await setPolicyName(rendered, 'testy0'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [ - i18nTexts.editPolicy.errors.policyNameAlreadyUsedErrorMessage, - ]); - }); - test('should show error when trying to save policy name with comma', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'my,policy'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.policyNameContainsInvalidChars]); - }); - test('should show error when trying to save policy name starting with underscore', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, '_mypolicy'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [ - i18nTexts.editPolicy.errors.policyNameStartsWithUnderscoreErrorMessage, - ]); - }); - test('should show correct json in policy flyout', async () => { - const rendered = mountWithIntl( - true }} - /> - ); - - await act(async () => { - findTestSubject(rendered, 'requestButton').simulate('click'); - }); - rendered.update(); - - const json = rendered.find(`code`).text(); - const expected = `PUT _ilm/policy/my-policy\n${JSON.stringify( - { - policy: { - phases: { - hot: { - actions: { - rollover: { - max_age: '30d', - max_size: '50gb', - }, - }, - min_age: '0ms', - }, - }, - }, - }, - null, - 2 - )}`; - expect(json).toBe(expected); - }); - }); - describe('hot phase', () => { - test('should show errors when trying to save with no max size, no max age and no max docs', async () => { - const rendered = mountWithIntl(component); - await noDefaultRollover(rendered); - expect(findTestSubject(rendered, 'rolloverSettingsRequired').exists()).toBeFalsy(); - await setPolicyName(rendered, 'mypolicy'); - const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); - await act(async () => { - maxSizeInput.simulate('change', { target: { value: '' } }); - }); - waitForFormLibValidation(rendered); - const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge'); - await act(async () => { - maxAgeInput.simulate('change', { target: { value: '' } }); - }); - waitForFormLibValidation(rendered); - const maxDocsInput = findTestSubject(rendered, 'hot-selectedMaxDocuments'); - await act(async () => { - maxDocsInput.simulate('change', { target: { value: '' } }); - }); - waitForFormLibValidation(rendered); - await save(rendered); - expect(findTestSubject(rendered, 'rolloverSettingsRequired').exists()).toBeTruthy(); - }); - test('should show number above 0 required error when trying to save with -1 for max size', async () => { - const rendered = mountWithIntl(component); - await setPolicyName(rendered, 'mypolicy'); - await noDefaultRollover(rendered); - const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); - await act(async () => { - maxSizeInput.simulate('change', { target: { value: '-1' } }); - }); - waitForFormLibValidation(rendered); - rendered.update(); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); - }); - test('should show number above 0 required error when trying to save with 0 for max size', async () => { - const rendered = mountWithIntl(component); - await setPolicyName(rendered, 'mypolicy'); - await noDefaultRollover(rendered); - const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); - await act(async () => { - maxSizeInput.simulate('change', { target: { value: '-1' } }); - }); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); - }); - test('should show number above 0 required error when trying to save with -1 for max age', async () => { - const rendered = mountWithIntl(component); - await setPolicyName(rendered, 'mypolicy'); - await noDefaultRollover(rendered); - const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge'); - await act(async () => { - maxAgeInput.simulate('change', { target: { value: '-1' } }); - }); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); - }); - test('should show number above 0 required error when trying to save with 0 for max age', async () => { - const rendered = mountWithIntl(component); - await setPolicyName(rendered, 'mypolicy'); - await noDefaultRollover(rendered); - const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge'); - await act(async () => { - maxAgeInput.simulate('change', { target: { value: '0' } }); - }); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); - }); - test('should show forcemerge input when rollover enabled', async () => { - const rendered = mountWithIntl(component); - await setPolicyName(rendered, 'mypolicy'); - expect(findTestSubject(rendered, 'hot-forceMergeSwitch').exists()).toBeTruthy(); - }); - test('should hide forcemerge input when rollover is disabled', async () => { - const rendered = mountWithIntl(component); - await setPolicyName(rendered, 'mypolicy'); - await noRollover(rendered); - waitForFormLibValidation(rendered); - expect(findTestSubject(rendered, 'hot-forceMergeSwitch').exists()).toBeFalsy(); - }); - test('should show positive number required above zero error when trying to save hot phase with 0 for force merge', async () => { - const rendered = mountWithIntl(component); - await setPolicyName(rendered, 'mypolicy'); - act(() => { - findTestSubject(rendered, 'hot-forceMergeSwitch').simulate('click'); - }); - rendered.update(); - const forcemergeInput = findTestSubject(rendered, 'hot-selectedForceMergeSegments'); - await act(async () => { - forcemergeInput.simulate('change', { target: { value: '0' } }); - }); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); - }); - test('should show positive number above 0 required error when trying to save hot phase with -1 for force merge', async () => { - const rendered = mountWithIntl(component); - await setPolicyName(rendered, 'mypolicy'); - findTestSubject(rendered, 'hot-forceMergeSwitch').simulate('click'); - rendered.update(); - const forcemergeInput = findTestSubject(rendered, 'hot-selectedForceMergeSegments'); - await act(async () => { - forcemergeInput.simulate('change', { target: { value: '-1' } }); - }); - waitForFormLibValidation(rendered); - await save(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); - }); - test('should show positive number required error when trying to save with -1 for index priority', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - - await setPhaseIndexPriority(rendered, 'hot', '-1'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); - }); - - test("doesn't show min age input", async () => { - const rendered = mountWithIntl(component); - expect(findTestSubject(rendered, 'hot-selectedMinimumAge').exists()).toBeFalsy(); - }); - }); - describe('warm phase', () => { - beforeEach(() => { - server.respondImmediately = true; - http.setupNodeListResponse(); - httpRequestsMockHelpers.setNodesDetailsResponse('attribute:true', [ - { nodeId: 'testNodeId', stats: { name: 'testNodeName', host: 'testHost' } }, - ]); - }); - - test('should show number required error when trying to save empty warm phase', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - await setPhaseAfter(rendered, 'warm', ''); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); - }); - test('should allow 0 for phase timing', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - await setPhaseAfter(rendered, 'warm', '0'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, []); - }); - test('should show positive number required error when trying to save warm phase with -1 for after', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - await setPhaseAfter(rendered, 'warm', '-1'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); - }); - test('should show positive number required error when trying to save warm phase with -1 for index priority', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - await setPhaseAfter(rendered, 'warm', '1'); - await setPhaseAfter(rendered, 'warm', '-1'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); - }); - test('should show positive number required above zero error when trying to save warm phase with 0 for shrink', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - act(() => { - findTestSubject(rendered, 'warm-shrinkSwitch').simulate('click'); - }); - rendered.update(); - await setPhaseAfter(rendered, 'warm', '1'); - const shrinkInput = findTestSubject(rendered, 'warm-primaryShardCount'); - await act(async () => { - shrinkInput.simulate('change', { target: { value: '0' } }); - }); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); - }); - test('should show positive number above 0 required error when trying to save warm phase with -1 for shrink', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - await setPhaseAfter(rendered, 'warm', '1'); - act(() => { - findTestSubject(rendered, 'warm-shrinkSwitch').simulate('click'); - }); - rendered.update(); - const shrinkInput = findTestSubject(rendered, 'warm-primaryShardCount'); - await act(async () => { - shrinkInput.simulate('change', { target: { value: '-1' } }); - }); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); - }); - test('should show positive number required above zero error when trying to save warm phase with 0 for force merge', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - await setPhaseAfter(rendered, 'warm', '1'); - act(() => { - findTestSubject(rendered, 'warm-forceMergeSwitch').simulate('click'); - }); - rendered.update(); - const forcemergeInput = findTestSubject(rendered, 'warm-selectedForceMergeSegments'); - await act(async () => { - forcemergeInput.simulate('change', { target: { value: '0' } }); - }); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); - }); - test('should show positive number above 0 required error when trying to save warm phase with -1 for force merge', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - await setPhaseAfter(rendered, 'warm', '1'); - await act(async () => { - findTestSubject(rendered, 'warm-forceMergeSwitch').simulate('click'); - }); - rendered.update(); - const forcemergeInput = findTestSubject(rendered, 'warm-selectedForceMergeSegments'); - await act(async () => { - forcemergeInput.simulate('change', { target: { value: '-1' } }); - }); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); - }); - test('should show spinner for node attributes input when loading', async () => { - server.respondImmediately = false; - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy(); - expect(findTestSubject(rendered, 'warm-dataTierAllocationControls').exists()).toBeTruthy(); - expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy(); - expect(getNodeAttributeSelect(rendered, 'warm').exists()).toBeFalsy(); - }); - test('should show warning instead of node attributes input when none exist', async () => { - http.setupNodeListResponse({ - nodesByAttributes: {}, - nodesByRoles: { data: ['node1'] }, - isUsingDeprecatedDataRoleConfig: false, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - await openNodeAttributesSection(rendered, 'warm'); - expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeTruthy(); - expect(getNodeAttributeSelect(rendered, 'warm').exists()).toBeFalsy(); - }); - test('should show node attributes input when attributes exist', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - await openNodeAttributesSection(rendered, 'warm'); - expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeFalsy(); - const nodeAttributesSelect = getNodeAttributeSelect(rendered, 'warm'); - expect(nodeAttributesSelect.exists()).toBeTruthy(); - expect(nodeAttributesSelect.find('option').length).toBe(2); - }); - test('should show view node attributes link when attribute selected and show flyout when clicked', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - await openNodeAttributesSection(rendered, 'warm'); - expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeFalsy(); - const nodeAttributesSelect = getNodeAttributeSelect(rendered, 'warm'); - expect(nodeAttributesSelect.exists()).toBeTruthy(); - expect(findTestSubject(rendered, 'warm-viewNodeDetailsFlyoutButton').exists()).toBeFalsy(); - expect(nodeAttributesSelect.find('option').length).toBe(2); - await act(async () => { - nodeAttributesSelect.simulate('change', { target: { value: 'attribute:true' } }); - }); - rendered.update(); - const flyoutButton = findTestSubject(rendered, 'warm-viewNodeDetailsFlyoutButton'); - expect(flyoutButton.exists()).toBeTruthy(); - await act(async () => { - await flyoutButton.simulate('click'); - }); - rendered.update(); - expect(rendered.find('.euiFlyout').exists()).toBeTruthy(); - }); - test('should show default allocation warning when no node roles are found', async () => { - http.setupNodeListResponse({ - nodesByAttributes: {}, - nodesByRoles: {}, - isUsingDeprecatedDataRoleConfig: false, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - expect(findTestSubject(rendered, 'defaultAllocationWarning').exists()).toBeTruthy(); - }); - test('should show default allocation notice when hot tier exists, but not warm tier', async () => { - http.setupNodeListResponse({ - nodesByAttributes: {}, - nodesByRoles: { data_hot: ['test'], data_cold: ['test'] }, - isUsingDeprecatedDataRoleConfig: false, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeTruthy(); - }); - test('should not show default allocation notice when node with "data" role exists', async () => { - http.setupNodeListResponse({ - nodesByAttributes: {}, - nodesByRoles: { data: ['test'] }, - isUsingDeprecatedDataRoleConfig: false, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeFalsy(); - }); - - test('shows min age input only when enabled', async () => { - const rendered = mountWithIntl(component); - expect(findTestSubject(rendered, 'warm-selectedMinimumAge').exists()).toBeFalsy(); - await activatePhase(rendered, 'warm'); - expect(findTestSubject(rendered, 'warm-selectedMinimumAge').exists()).toBeTruthy(); - }); - }); - describe('cold phase', () => { - beforeEach(() => { - server.respondImmediately = true; - http.setupNodeListResponse(); - httpRequestsMockHelpers.setNodesDetailsResponse('attribute:true', [ - { nodeId: 'testNodeId', stats: { name: 'testNodeName', host: 'testHost' } }, - ]); - }); - test('should allow 0 for phase timing', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'cold'); - await setPhaseAfter(rendered, 'cold', '0'); - waitForFormLibValidation(rendered); - rendered.update(); - expectedErrorMessages(rendered, []); - }); - test('should show positive number required error when trying to save cold phase with -1 for after', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'cold'); - await setPhaseAfter(rendered, 'cold', '-1'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); - }); - test('should show spinner for node attributes input when loading', async () => { - server.respondImmediately = false; - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'cold'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy(); - expect(findTestSubject(rendered, 'cold-dataTierAllocationControls').exists()).toBeTruthy(); - expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy(); - expect(getNodeAttributeSelect(rendered, 'cold').exists()).toBeFalsy(); - }); - test('should show warning instead of node attributes input when none exist', async () => { - http.setupNodeListResponse({ - nodesByAttributes: {}, - nodesByRoles: { data: ['node1'] }, - isUsingDeprecatedDataRoleConfig: false, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'cold'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - await openNodeAttributesSection(rendered, 'cold'); - expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeTruthy(); - expect(getNodeAttributeSelect(rendered, 'cold').exists()).toBeFalsy(); - }); - test('should show node attributes input when attributes exist', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'cold'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - await openNodeAttributesSection(rendered, 'cold'); - expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeFalsy(); - const nodeAttributesSelect = getNodeAttributeSelect(rendered, 'cold'); - expect(nodeAttributesSelect.exists()).toBeTruthy(); - expect(nodeAttributesSelect.find('option').length).toBe(2); - }); - test('should show view node attributes link when attribute selected and show flyout when clicked', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'cold'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - await openNodeAttributesSection(rendered, 'cold'); - expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeFalsy(); - const nodeAttributesSelect = getNodeAttributeSelect(rendered, 'cold'); - expect(nodeAttributesSelect.exists()).toBeTruthy(); - expect(findTestSubject(rendered, 'cold-viewNodeDetailsFlyoutButton').exists()).toBeFalsy(); - expect(nodeAttributesSelect.find('option').length).toBe(2); - nodeAttributesSelect.simulate('change', { target: { value: 'attribute:true' } }); - rendered.update(); - const flyoutButton = findTestSubject(rendered, 'cold-viewNodeDetailsFlyoutButton'); - expect(flyoutButton.exists()).toBeTruthy(); - await act(async () => { - await flyoutButton.simulate('click'); - }); - rendered.update(); - expect(rendered.find('.euiFlyout').exists()).toBeTruthy(); - }); - test('should show positive number required error when trying to save with -1 for index priority', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'cold'); - await setPhaseAfter(rendered, 'cold', '1'); - await setPhaseIndexPriority(rendered, 'cold', '-1'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); - }); - test('should show default allocation warning when no node roles are found', async () => { - http.setupNodeListResponse({ - nodesByAttributes: {}, - nodesByRoles: {}, - isUsingDeprecatedDataRoleConfig: false, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'cold'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - expect(findTestSubject(rendered, 'defaultAllocationWarning').exists()).toBeTruthy(); - }); - test('should show default allocation notice when warm or hot tiers exists, but not cold tier', async () => { - http.setupNodeListResponse({ - nodesByAttributes: {}, - nodesByRoles: { data_hot: ['test'], data_warm: ['test'] }, - isUsingDeprecatedDataRoleConfig: false, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'cold'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeTruthy(); - }); - test('should not show default allocation notice when node with "data" role exists', async () => { - http.setupNodeListResponse({ - nodesByAttributes: {}, - nodesByRoles: { data: ['test'] }, - isUsingDeprecatedDataRoleConfig: false, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'cold'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeFalsy(); - }); - - test('shows min age input only when enabled', async () => { - const rendered = mountWithIntl(component); - expect(findTestSubject(rendered, 'cold-selectedMinimumAge').exists()).toBeFalsy(); - await activatePhase(rendered, 'cold'); - expect(findTestSubject(rendered, 'cold-selectedMinimumAge').exists()).toBeTruthy(); - }); - }); - describe('delete phase', () => { - test('should allow 0 for phase timing', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activateDeletePhase(rendered); - await setPhaseAfter(rendered, 'delete', '0'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, []); - }); - test('should show positive number required error when trying to save delete phase with -1 for after', async () => { - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activateDeletePhase(rendered); - await setPhaseAfter(rendered, 'delete', '-1'); - waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); - }); - - test('is hidden when disabled', async () => { - const rendered = mountWithIntl(component); - expect(findTestSubject(rendered, 'delete-phaseContent').exists()).toBeFalsy(); - await activateDeletePhase(rendered); - expect(findTestSubject(rendered, 'delete-phaseContent').exists()).toBeTruthy(); - }); - }); - describe('not on cloud', () => { - beforeEach(() => { - server.respondImmediately = true; - }); - test('should show all allocation options, even if using legacy config', async () => { - http.setupNodeListResponse({ - nodesByAttributes: { test: ['123'] }, - nodesByRoles: { data: ['test'], data_hot: ['test'], data_warm: ['test'] }, - isUsingDeprecatedDataRoleConfig: true, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - - // Assert that default, custom and 'none' options exist - findTestSubject(rendered, 'dataTierSelect').simulate('click'); - expect(findTestSubject(rendered, 'defaultDataAllocationOption').exists()).toBeTruthy(); - expect(findTestSubject(rendered, 'customDataAllocationOption').exists()).toBeTruthy(); - expect(findTestSubject(rendered, 'noneDataAllocationOption').exists()).toBeTruthy(); - }); - }); - describe('on cloud', () => { - beforeEach(() => { - component = ( - true }} - /> - ); - ({ http } = editPolicyHelpers.setup()); - ({ server, httpRequestsMockHelpers } = http); - server.respondImmediately = true; - - httpRequestsMockHelpers.setPoliciesResponse(policies); - }); - - describe('with deprecated data role config', () => { - test('should hide data tier option on cloud using legacy node role configuration', async () => { - http.setupNodeListResponse({ - nodesByAttributes: { test: ['123'] }, - // On cloud, if using legacy config there will not be any "data_*" roles set. - nodesByRoles: { data: ['test'] }, - isUsingDeprecatedDataRoleConfig: true, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - - // Assert that default, custom and 'none' options exist - findTestSubject(rendered, 'dataTierSelect').simulate('click'); - expect(findTestSubject(rendered, 'defaultDataAllocationOption').exists()).toBeFalsy(); - expect(findTestSubject(rendered, 'customDataAllocationOption').exists()).toBeTruthy(); - expect(findTestSubject(rendered, 'noneDataAllocationOption').exists()).toBeTruthy(); - }); - }); - - describe('with node role config', () => { - test('should show off, custom and data role options on cloud with data roles', async () => { - http.setupNodeListResponse({ - nodesByAttributes: { test: ['123'] }, - nodesByRoles: { data: ['test'], data_hot: ['test'], data_warm: ['test'] }, - isUsingDeprecatedDataRoleConfig: false, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'warm'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - - findTestSubject(rendered, 'dataTierSelect').simulate('click'); - expect(findTestSubject(rendered, 'defaultDataAllocationOption').exists()).toBeTruthy(); - expect(findTestSubject(rendered, 'customDataAllocationOption').exists()).toBeTruthy(); - expect(findTestSubject(rendered, 'noneDataAllocationOption').exists()).toBeTruthy(); - // We should not be showing the call-to-action for users to activate data tiers in cloud - expect(findTestSubject(rendered, 'cloudDataTierCallout').exists()).toBeFalsy(); - }); - - test('should show cloud notice when cold tier nodes do not exist', async () => { - http.setupNodeListResponse({ - nodesByAttributes: {}, - nodesByRoles: { data: ['test'], data_hot: ['test'], data_warm: ['test'] }, - isUsingDeprecatedDataRoleConfig: false, - }); - const rendered = mountWithIntl(component); - await noRollover(rendered); - await setPolicyName(rendered, 'mypolicy'); - await activatePhase(rendered, 'cold'); - expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - expect(findTestSubject(rendered, 'cloudDataTierCallout').exists()).toBeTruthy(); - // Assert that other notices are not showing - expect(findTestSubject(rendered, 'defaultAllocationNotice').exists()).toBeFalsy(); - expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeFalsy(); - }); - }); - }); -}); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/edit_policy.ts b/x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/edit_policy.ts deleted file mode 100644 index 49fd651ca9453f..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/edit_policy.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { init as initHttpRequests } from './http_requests'; - -export type EditPolicySetup = ReturnType; - -export const setup = () => { - const { httpRequestsMockHelpers, server } = initHttpRequests(); - - const setupNodeListResponse = ( - response: Record = { - nodesByAttributes: { 'attribute:true': ['node1'] }, - nodesByRoles: { data: ['node1'] }, - } - ) => { - httpRequestsMockHelpers.setNodesListResponse(response); - }; - - return { - http: { - setupNodeListResponse, - httpRequestsMockHelpers, - server, - }, - }; -}; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/http_requests.ts b/x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/http_requests.ts deleted file mode 100644 index ea6e2af87a6d96..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/http_requests.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import sinon, { SinonFakeServer } from 'sinon'; - -export type HttpResponse = Record | any[]; - -const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { - const setPoliciesResponse = (response: HttpResponse = []) => { - server.respondWith('/api/index_lifecycle_management/policies', [ - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify(response), - ]); - }; - - const setNodesListResponse = (response: HttpResponse = []) => { - server.respondWith('/api/index_lifecycle_management/nodes/list', [ - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify(response), - ]); - }; - - const setNodesDetailsResponse = (nodeAttributes: string, response: HttpResponse = []) => { - server.respondWith(`/api/index_lifecycle_management/nodes/${nodeAttributes}/details`, [ - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify(response), - ]); - }; - - return { - setPoliciesResponse, - setNodesListResponse, - setNodesDetailsResponse, - }; -}; - -export type HttpRequestMockHelpers = ReturnType; - -export const init = () => { - const server = sinon.fakeServer.create(); - - // Define default response for unhandled requests. - // We make requests to APIs which don't impact the component under test, e.g. UI metric telemetry, - // and we can mock them all with a 200 instead of mocking each one individually. - server.respondWith([200, {}, 'DefaultSinonMockServerResponse']); - - const httpRequestsMockHelpers = registerHttpRequestMockHelpers(server); - - return { - server, - httpRequestsMockHelpers, - }; -}; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/index.ts b/x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/index.ts deleted file mode 100644 index 95a45d12e23a2a..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/helpers/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as editPolicyHelpers from './edit_policy'; - -export { HttpRequestMockHelpers, init } from './http_requests'; - -export { editPolicyHelpers }; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx similarity index 92% rename from x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.tsx rename to x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx index 803560c67cf285..7733d547e34728 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/policy_table.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx @@ -15,14 +15,14 @@ import { fatalErrorsServiceMock, injectedMetadataServiceMock, scopedHistoryMock, -} from '../../../../../src/core/public/mocks'; -import { HttpService } from '../../../../../src/core/public/http'; -import { usageCollectionPluginMock } from '../../../../../src/plugins/usage_collection/public/mocks'; +} from '../../../../src/core/public/mocks'; +import { HttpService } from '../../../../src/core/public/http'; +import { usageCollectionPluginMock } from '../../../../src/plugins/usage_collection/public/mocks'; -import { PolicyFromES } from '../../common/types'; -import { PolicyTable } from '../../public/application/sections/policy_table/policy_table'; -import { init as initHttp } from '../../public/application/services/http'; -import { init as initUiMetric } from '../../public/application/services/ui_metric'; +import { PolicyFromES } from '../common/types'; +import { PolicyTable } from '../public/application/sections/policy_table/policy_table'; +import { init as initHttp } from '../public/application/services/http'; +import { init as initUiMetric } from '../public/application/services/ui_metric'; initHttp( new HttpService().setup({ diff --git a/x-pack/plugins/index_lifecycle_management/common/types/api.ts b/x-pack/plugins/index_lifecycle_management/common/types/api.ts index 81190acd01ad1b..6d4e11c58f9bb2 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/api.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/api.ts @@ -20,6 +20,16 @@ export interface ListNodesRouteResponse { isUsingDeprecatedDataRoleConfig: boolean; } +export interface NodesDetails { + nodeId: string; + stats: { + name: string; + host: string; + }; +} + +export type NodesDetailsResponse = NodesDetails[]; + export interface ListSnapshotReposResponse { /** * An array of repository names From 1fa742d0ceeb543fae005bc576318f97e85df2a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Fri, 19 Feb 2021 08:57:14 -0500 Subject: [PATCH 73/93] [APM] Kql Search Bar suggests values outside the selected time range (#91918) --- .../apm/server/lib/index_pattern/get_dynamic_index_pattern.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts index cb6183510ad168..8b81101fd2f39e 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts @@ -15,6 +15,7 @@ import { withApmSpan } from '../../utils/with_apm_span'; export interface IndexPatternTitleAndFields { title: string; + timeFieldName: string; fields: FieldDescriptor[]; } @@ -52,6 +53,7 @@ export const getDynamicIndexPattern = ({ const indexPattern: IndexPatternTitleAndFields = { fields, + timeFieldName: '@timestamp', title: indexPatternTitle, }; From 8b909cedc822569911cd794a35e172e11998dae6 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Fri, 19 Feb 2021 14:05:47 +0000 Subject: [PATCH 74/93] [Search Source] Do not request unmapped fields if source filters are provided (#91921) --- .../data/common/search/search_source/search_source.test.ts | 5 +---- .../data/common/search/search_source/search_source.ts | 7 +------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 030e620bea34b4..fd97a3d3381a99 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -418,10 +418,7 @@ describe('SearchSource', () => { searchSource.setField('fields', [{ field: '*', include_unmapped: 'true' }]); const request = await searchSource.getSearchRequestBody(); - expect(request.fields).toEqual([ - { field: 'field1', include_unmapped: 'true' }, - { field: 'field2', include_unmapped: 'true' }, - ]); + expect(request.fields).toEqual([{ field: 'field1' }, { field: 'field2' }]); }); test('returns all scripted fields when one fields entry is *', async () => { diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 118bb04c1742b4..486f2b36674536 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -503,12 +503,7 @@ export class SearchSource { // we need to get the list of fields from an index pattern return fields .filter((fld: IndexPatternField) => filterSourceFields(fld.name)) - .map((fld: IndexPatternField) => ({ - field: fld.name, - ...((wildcardField as Record)?.include_unmapped && { - include_unmapped: (wildcardField as Record).include_unmapped, - }), - })); + .map((fld: IndexPatternField) => ({ field: fld.name })); } private getFieldFromDocValueFieldsOrIndexPattern( From 4d34a13babd74d0c43066993468d4c69527254d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 19 Feb 2021 15:06:33 +0100 Subject: [PATCH 75/93] [Logs UI] Replace dependencies in the infra bundle (#91503) --- packages/kbn-optimizer/limits.yml | 2 +- x-pack/plugins/infra/common/formatters/index.ts | 3 +-- x-pack/plugins/infra/public/apps/legacy_app.tsx | 10 ++++------ x-pack/plugins/infra/public/hooks/use_link_props.tsx | 11 +++++------ 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 1a157624d7a8ab..1ebd0a9b83bd04 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -34,7 +34,7 @@ pageLoadAssetSize: indexLifecycleManagement: 107090 indexManagement: 140608 indexPatternManagement: 28222 - infra: 204800 + infra: 184320 fleet: 415829 ingestPipelines: 58003 inputControlVis: 172675 diff --git a/x-pack/plugins/infra/common/formatters/index.ts b/x-pack/plugins/infra/common/formatters/index.ts index 61e01aa7e68376..a4aeee80848241 100644 --- a/x-pack/plugins/infra/common/formatters/index.ts +++ b/x-pack/plugins/infra/common/formatters/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import Mustache from 'mustache'; import { createBytesFormatter } from './bytes'; import { formatNumber } from './number'; import { formatPercent } from './percent'; @@ -34,5 +33,5 @@ export const createFormatter = (format: InventoryFormatterType, template: string } const fmtFn = FORMATTERS[format]; const value = fmtFn(Number(val)); - return Mustache.render(template, { value }); + return template.replace(/{{value}}/g, value); }; diff --git a/x-pack/plugins/infra/public/apps/legacy_app.tsx b/x-pack/plugins/infra/public/apps/legacy_app.tsx index 50f24c2042c13c..8aeb99c4266519 100644 --- a/x-pack/plugins/infra/public/apps/legacy_app.tsx +++ b/x-pack/plugins/infra/public/apps/legacy_app.tsx @@ -11,7 +11,6 @@ import { AppMountParameters } from 'kibana/public'; import React from 'react'; import ReactDOM from 'react-dom'; import { Route, RouteProps, Router, Switch } from 'react-router-dom'; -import url from 'url'; // This exists purely to facilitate legacy app/infra URL redirects. // It will be removed in 8.0.0. @@ -79,11 +78,10 @@ const LegacyApp: React.FunctionComponent<{ history: History }> = ({ his nextPath = nextPathParts[0]; nextSearch = nextPathParts[1] ? nextPathParts[1] : undefined; - let nextUrl = url.format({ - pathname: `${nextBasePath}/${nextPath}`, - hash: undefined, - search: nextSearch, - }); + const builtPathname = `${nextBasePath}/${nextPath}`; + const builtSearch = nextSearch ? `?${nextSearch}` : ''; + + let nextUrl = `${builtPathname}${builtSearch}`; nextUrl = nextUrl.replace('//', '/'); diff --git a/x-pack/plugins/infra/public/hooks/use_link_props.tsx b/x-pack/plugins/infra/public/hooks/use_link_props.tsx index 72a538cd56281e..7546f9f0c9f797 100644 --- a/x-pack/plugins/infra/public/hooks/use_link_props.tsx +++ b/x-pack/plugins/infra/public/hooks/use_link_props.tsx @@ -7,7 +7,6 @@ import { useMemo } from 'react'; import { stringify } from 'query-string'; -import url from 'url'; import { url as urlUtils } from '../../../../../src/plugins/kibana_utils/public'; import { usePrefixPathWithBasepath } from './use_prefix_path_with_basepath'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; @@ -58,11 +57,11 @@ export const useLinkProps = ( }, [pathname, encodedSearch]); const href = useMemo(() => { - const link = url.format({ - pathname, - hash: mergedHash, - search: !hash ? encodedSearch : undefined, - }); + const builtPathname = pathname ?? ''; + const builtHash = mergedHash ? `#${mergedHash}` : ''; + const builtSearch = !hash ? (encodedSearch ? `?${encodedSearch}` : '') : ''; + + const link = `${builtPathname}${builtSearch}${builtHash}`; return prefixer(app, link); }, [mergedHash, hash, encodedSearch, pathname, prefixer, app]); From eea6f82e4e436abdf28266127a801a57e3c2dcad Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Fri, 19 Feb 2021 08:00:01 -0700 Subject: [PATCH 76/93] [Maps] Use new index patterns service for Maps telemetry (#86703) Co-authored-by: Matt Kime --- ...rver.indexpatternsservice._constructor_.md | 20 +++ ...-server.indexpatternsservice.clearcache.md | 13 ++ ...data-server.indexpatternsservice.create.md | 27 +++ ...rver.indexpatternsservice.createandsave.md | 26 +++ ....indexpatternsservice.createsavedobject.md | 25 +++ ...data-server.indexpatternsservice.delete.md | 24 +++ ...tternsservice.ensuredefaultindexpattern.md | 11 ++ ...er.indexpatternsservice.fieldarraytomap.md | 13 ++ ...s-data-server.indexpatternsservice.find.md | 13 ++ ...ns-data-server.indexpatternsservice.get.md | 13 ++ ...ta-server.indexpatternsservice.getcache.md | 11 ++ ...-server.indexpatternsservice.getdefault.md | 13 ++ ...atternsservice.getfieldsforindexpattern.md | 13 ++ ...dexpatternsservice.getfieldsforwildcard.md | 13 ++ ...data-server.indexpatternsservice.getids.md | 13 ++ ...er.indexpatternsservice.getidswithtitle.md | 16 ++ ...a-server.indexpatternsservice.gettitles.md | 13 ++ ...lugins-data-server.indexpatternsservice.md | 35 +++- ...rver.indexpatternsservice.refreshfields.md | 13 ++ ....indexpatternsservice.savedobjecttospec.md | 13 ++ ...-server.indexpatternsservice.setdefault.md | 13 ++ ....indexpatternsservice.updatesavedobject.md | 26 +++ ...ata-server.indexpatternsserviceprovider.md | 19 ++ ...ver.indexpatternsserviceprovider.setup.md} | 4 +- ...ver.indexpatternsserviceprovider.start.md} | 4 +- .../kibana-plugin-plugins-data-server.md | 1 + ...plugin-plugins-data-server.plugin.start.md | 4 +- src/plugins/data/server/index.ts | 4 +- .../index_patterns/index_patterns_service.ts | 2 +- src/plugins/data/server/server.api.md | 78 +++++++-- .../maps/server/kibana_server_services.ts | 18 +- .../maps_telemetry/maps_telemetry.test.js | 61 ++++++- .../server/maps_telemetry/maps_telemetry.ts | 164 +++++++----------- .../sample_index_pattern_saved_objects.json | 59 ------- x-pack/plugins/maps/server/plugin.ts | 13 +- 35 files changed, 616 insertions(+), 192 deletions(-) create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice._constructor_.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.clearcache.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.create.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.createandsave.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.createsavedobject.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.delete.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.ensuredefaultindexpattern.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.fieldarraytomap.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.find.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.get.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getcache.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getdefault.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforindexpattern.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforwildcard.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getids.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getidswithtitle.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.gettitles.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.refreshfields.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.savedobjecttospec.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setdefault.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.updatesavedobject.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md rename docs/development/plugins/data/server/{kibana-plugin-plugins-data-server.indexpatternsservice.setup.md => kibana-plugin-plugins-data-server.indexpatternsserviceprovider.setup.md} (67%) rename docs/development/plugins/data/server/{kibana-plugin-plugins-data-server.indexpatternsservice.start.md => kibana-plugin-plugins-data-server.indexpatternsserviceprovider.start.md} (75%) delete mode 100644 x-pack/plugins/maps/server/maps_telemetry/test_resources/sample_index_pattern_saved_objects.json diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice._constructor_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice._constructor_.md new file mode 100644 index 00000000000000..86e879eecc5a96 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice._constructor_.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [(constructor)](./kibana-plugin-plugins-data-server.indexpatternsservice._constructor_.md) + +## IndexPatternsService.(constructor) + +Constructs a new instance of the `IndexPatternsService` class + +Signature: + +```typescript +constructor({ uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, }: IndexPatternsServiceDeps); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, } | IndexPatternsServiceDeps | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.clearcache.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.clearcache.md new file mode 100644 index 00000000000000..eb0e92f3760c83 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.clearcache.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [clearCache](./kibana-plugin-plugins-data-server.indexpatternsservice.clearcache.md) + +## IndexPatternsService.clearCache property + +Clear index pattern list cache + +Signature: + +```typescript +clearCache: (id?: string | undefined) => void; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.create.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.create.md new file mode 100644 index 00000000000000..e5cc7c2e433ca3 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.create.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [create](./kibana-plugin-plugins-data-server.indexpatternsservice.create.md) + +## IndexPatternsService.create() method + +Create a new index pattern instance + +Signature: + +```typescript +create(spec: IndexPatternSpec, skipFetchFields?: boolean): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| spec | IndexPatternSpec | | +| skipFetchFields | boolean | | + +Returns: + +`Promise` + +IndexPattern + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.createandsave.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.createandsave.md new file mode 100644 index 00000000000000..9b6e3a82528d59 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.createandsave.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [createAndSave](./kibana-plugin-plugins-data-server.indexpatternsservice.createandsave.md) + +## IndexPatternsService.createAndSave() method + +Create a new index pattern and save it right away + +Signature: + +```typescript +createAndSave(spec: IndexPatternSpec, override?: boolean, skipFetchFields?: boolean): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| spec | IndexPatternSpec | | +| override | boolean | | +| skipFetchFields | boolean | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.createsavedobject.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.createsavedobject.md new file mode 100644 index 00000000000000..6ffadf648f5b6f --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.createsavedobject.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [createSavedObject](./kibana-plugin-plugins-data-server.indexpatternsservice.createsavedobject.md) + +## IndexPatternsService.createSavedObject() method + +Save a new index pattern + +Signature: + +```typescript +createSavedObject(indexPattern: IndexPattern, override?: boolean): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indexPattern | IndexPattern | | +| override | boolean | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.delete.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.delete.md new file mode 100644 index 00000000000000..929a8038494284 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.delete.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [delete](./kibana-plugin-plugins-data-server.indexpatternsservice.delete.md) + +## IndexPatternsService.delete() method + +Deletes an index pattern from .kibana index + +Signature: + +```typescript +delete(indexPatternId: string): Promise<{}>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indexPatternId | string | | + +Returns: + +`Promise<{}>` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.ensuredefaultindexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.ensuredefaultindexpattern.md new file mode 100644 index 00000000000000..c4f6b61e4feb4c --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.ensuredefaultindexpattern.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [ensureDefaultIndexPattern](./kibana-plugin-plugins-data-server.indexpatternsservice.ensuredefaultindexpattern.md) + +## IndexPatternsService.ensureDefaultIndexPattern property + +Signature: + +```typescript +ensureDefaultIndexPattern: EnsureDefaultIndexPattern; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.fieldarraytomap.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.fieldarraytomap.md new file mode 100644 index 00000000000000..e0b27c317ff74c --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.fieldarraytomap.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [fieldArrayToMap](./kibana-plugin-plugins-data-server.indexpatternsservice.fieldarraytomap.md) + +## IndexPatternsService.fieldArrayToMap property + +Converts field array to map + +Signature: + +```typescript +fieldArrayToMap: (fields: FieldSpec[], fieldAttrs?: FieldAttrs | undefined) => Record; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.find.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.find.md new file mode 100644 index 00000000000000..35b94133462aa3 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.find.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [find](./kibana-plugin-plugins-data-server.indexpatternsservice.find.md) + +## IndexPatternsService.find property + +Find and load index patterns by title + +Signature: + +```typescript +find: (search: string, size?: number) => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.get.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.get.md new file mode 100644 index 00000000000000..874f1d1a490c77 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.get.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [get](./kibana-plugin-plugins-data-server.indexpatternsservice.get.md) + +## IndexPatternsService.get property + +Get an index pattern by id. Cache optimized + +Signature: + +```typescript +get: (id: string) => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getcache.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getcache.md new file mode 100644 index 00000000000000..821c06984e55e4 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getcache.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [getCache](./kibana-plugin-plugins-data-server.indexpatternsservice.getcache.md) + +## IndexPatternsService.getCache property + +Signature: + +```typescript +getCache: () => Promise[] | null | undefined>; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getdefault.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getdefault.md new file mode 100644 index 00000000000000..104e605e01bcbf --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getdefault.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [getDefault](./kibana-plugin-plugins-data-server.indexpatternsservice.getdefault.md) + +## IndexPatternsService.getDefault property + +Get default index pattern + +Signature: + +```typescript +getDefault: () => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforindexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforindexpattern.md new file mode 100644 index 00000000000000..db871c0bec83ca --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforindexpattern.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [getFieldsForIndexPattern](./kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforindexpattern.md) + +## IndexPatternsService.getFieldsForIndexPattern property + +Get field list by providing an index patttern (or spec) + +Signature: + +```typescript +getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforwildcard.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforwildcard.md new file mode 100644 index 00000000000000..0b2c6dbfdef8b2 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforwildcard.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [getFieldsForWildcard](./kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforwildcard.md) + +## IndexPatternsService.getFieldsForWildcard property + +Get field list by providing { pattern } + +Signature: + +```typescript +getFieldsForWildcard: (options: GetFieldsOptions) => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getids.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getids.md new file mode 100644 index 00000000000000..2f0fb56cc44575 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getids.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [getIds](./kibana-plugin-plugins-data-server.indexpatternsservice.getids.md) + +## IndexPatternsService.getIds property + +Get list of index pattern ids + +Signature: + +```typescript +getIds: (refresh?: boolean) => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getidswithtitle.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getidswithtitle.md new file mode 100644 index 00000000000000..6433c78483545c --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.getidswithtitle.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [getIdsWithTitle](./kibana-plugin-plugins-data-server.indexpatternsservice.getidswithtitle.md) + +## IndexPatternsService.getIdsWithTitle property + +Get list of index pattern ids with titles + +Signature: + +```typescript +getIdsWithTitle: (refresh?: boolean) => Promise>; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.gettitles.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.gettitles.md new file mode 100644 index 00000000000000..385e7f70d237a0 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.gettitles.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [getTitles](./kibana-plugin-plugins-data-server.indexpatternsservice.gettitles.md) + +## IndexPatternsService.getTitles property + +Get list of index pattern titles + +Signature: + +```typescript +getTitles: (refresh?: boolean) => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.md index 83e912d80dbd1b..d55a6e9b325a20 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.md @@ -7,13 +7,42 @@ Signature: ```typescript -export declare class IndexPatternsServiceProvider implements Plugin +export declare class IndexPatternsService ``` +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)({ uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, })](./kibana-plugin-plugins-data-server.indexpatternsservice._constructor_.md) | | Constructs a new instance of the IndexPatternsService class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [clearCache](./kibana-plugin-plugins-data-server.indexpatternsservice.clearcache.md) | | (id?: string | undefined) => void | Clear index pattern list cache | +| [ensureDefaultIndexPattern](./kibana-plugin-plugins-data-server.indexpatternsservice.ensuredefaultindexpattern.md) | | EnsureDefaultIndexPattern | | +| [fieldArrayToMap](./kibana-plugin-plugins-data-server.indexpatternsservice.fieldarraytomap.md) | | (fields: FieldSpec[], fieldAttrs?: FieldAttrs | undefined) => Record<string, FieldSpec> | Converts field array to map | +| [find](./kibana-plugin-plugins-data-server.indexpatternsservice.find.md) | | (search: string, size?: number) => Promise<IndexPattern[]> | Find and load index patterns by title | +| [get](./kibana-plugin-plugins-data-server.indexpatternsservice.get.md) | | (id: string) => Promise<IndexPattern> | Get an index pattern by id. Cache optimized | +| [getCache](./kibana-plugin-plugins-data-server.indexpatternsservice.getcache.md) | | () => Promise<SavedObject<IndexPatternSavedObjectAttrs>[] | null | undefined> | | +| [getDefault](./kibana-plugin-plugins-data-server.indexpatternsservice.getdefault.md) | | () => Promise<IndexPattern | null> | Get default index pattern | +| [getFieldsForIndexPattern](./kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforindexpattern.md) | | (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise<any> | Get field list by providing an index patttern (or spec) | +| [getFieldsForWildcard](./kibana-plugin-plugins-data-server.indexpatternsservice.getfieldsforwildcard.md) | | (options: GetFieldsOptions) => Promise<any> | Get field list by providing { pattern } | +| [getIds](./kibana-plugin-plugins-data-server.indexpatternsservice.getids.md) | | (refresh?: boolean) => Promise<string[]> | Get list of index pattern ids | +| [getIdsWithTitle](./kibana-plugin-plugins-data-server.indexpatternsservice.getidswithtitle.md) | | (refresh?: boolean) => Promise<Array<{
    id: string;
    title: string;
    }>> | Get list of index pattern ids with titles | +| [getTitles](./kibana-plugin-plugins-data-server.indexpatternsservice.gettitles.md) | | (refresh?: boolean) => Promise<string[]> | Get list of index pattern titles | +| [refreshFields](./kibana-plugin-plugins-data-server.indexpatternsservice.refreshfields.md) | | (indexPattern: IndexPattern) => Promise<void> | Refresh field list for a given index pattern | +| [savedObjectToSpec](./kibana-plugin-plugins-data-server.indexpatternsservice.savedobjecttospec.md) | | (savedObject: SavedObject<IndexPatternAttributes>) => IndexPatternSpec | Converts index pattern saved object to index pattern spec | +| [setDefault](./kibana-plugin-plugins-data-server.indexpatternsservice.setdefault.md) | | (id: string, force?: boolean) => Promise<void> | Optionally set default index pattern, unless force = true | + ## Methods | Method | Modifiers | Description | | --- | --- | --- | -| [setup(core, { expressions })](./kibana-plugin-plugins-data-server.indexpatternsservice.setup.md) | | | -| [start(core, { fieldFormats, logger })](./kibana-plugin-plugins-data-server.indexpatternsservice.start.md) | | | +| [create(spec, skipFetchFields)](./kibana-plugin-plugins-data-server.indexpatternsservice.create.md) | | Create a new index pattern instance | +| [createAndSave(spec, override, skipFetchFields)](./kibana-plugin-plugins-data-server.indexpatternsservice.createandsave.md) | | Create a new index pattern and save it right away | +| [createSavedObject(indexPattern, override)](./kibana-plugin-plugins-data-server.indexpatternsservice.createsavedobject.md) | | Save a new index pattern | +| [delete(indexPatternId)](./kibana-plugin-plugins-data-server.indexpatternsservice.delete.md) | | Deletes an index pattern from .kibana index | +| [updateSavedObject(indexPattern, saveAttempts, ignoreErrors)](./kibana-plugin-plugins-data-server.indexpatternsservice.updatesavedobject.md) | | Save existing index pattern. Will attempt to merge differences if there are conflicts | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.refreshfields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.refreshfields.md new file mode 100644 index 00000000000000..6b81447eca9eda --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.refreshfields.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [refreshFields](./kibana-plugin-plugins-data-server.indexpatternsservice.refreshfields.md) + +## IndexPatternsService.refreshFields property + +Refresh field list for a given index pattern + +Signature: + +```typescript +refreshFields: (indexPattern: IndexPattern) => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.savedobjecttospec.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.savedobjecttospec.md new file mode 100644 index 00000000000000..92ac4e556ae291 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.savedobjecttospec.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [savedObjectToSpec](./kibana-plugin-plugins-data-server.indexpatternsservice.savedobjecttospec.md) + +## IndexPatternsService.savedObjectToSpec property + +Converts index pattern saved object to index pattern spec + +Signature: + +```typescript +savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setdefault.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setdefault.md new file mode 100644 index 00000000000000..708d645a79f1a7 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setdefault.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [setDefault](./kibana-plugin-plugins-data-server.indexpatternsservice.setdefault.md) + +## IndexPatternsService.setDefault property + +Optionally set default index pattern, unless force = true + +Signature: + +```typescript +setDefault: (id: string, force?: boolean) => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.updatesavedobject.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.updatesavedobject.md new file mode 100644 index 00000000000000..17f261aebdc658 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.updatesavedobject.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [updateSavedObject](./kibana-plugin-plugins-data-server.indexpatternsservice.updatesavedobject.md) + +## IndexPatternsService.updateSavedObject() method + +Save existing index pattern. Will attempt to merge differences if there are conflicts + +Signature: + +```typescript +updateSavedObject(indexPattern: IndexPattern, saveAttempts?: number, ignoreErrors?: boolean): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indexPattern | IndexPattern | | +| saveAttempts | number | | +| ignoreErrors | boolean | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md new file mode 100644 index 00000000000000..d408f00e33c9e8 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsServiceProvider](./kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md) + +## IndexPatternsServiceProvider class + +Signature: + +```typescript +export declare class IndexPatternsServiceProvider implements Plugin +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [setup(core, { expressions })](./kibana-plugin-plugins-data-server.indexpatternsserviceprovider.setup.md) | | | +| [start(core, { fieldFormats, logger })](./kibana-plugin-plugins-data-server.indexpatternsserviceprovider.start.md) | | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.setup.md similarity index 67% rename from docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setup.md rename to docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.setup.md index 6cac0a806d2ecb..b5047d34efac1f 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setup.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.setup.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [setup](./kibana-plugin-plugins-data-server.indexpatternsservice.setup.md) +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsServiceProvider](./kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md) > [setup](./kibana-plugin-plugins-data-server.indexpatternsserviceprovider.setup.md) -## IndexPatternsService.setup() method +## IndexPatternsServiceProvider.setup() method Signature: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.start.md similarity index 75% rename from docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md rename to docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.start.md index 6528b1c213ccad..98f9310c6d98cc 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsserviceprovider.start.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [start](./kibana-plugin-plugins-data-server.indexpatternsservice.start.md) +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsServiceProvider](./kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md) > [start](./kibana-plugin-plugins-data-server.indexpatternsserviceprovider.start.md) -## IndexPatternsService.start() method +## IndexPatternsServiceProvider.start() method Signature: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 4739de481e0207..491babcdfdecfb 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -12,6 +12,7 @@ | [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) | | | [IndexPatternsFetcher](./kibana-plugin-plugins-data-server.indexpatternsfetcher.md) | | | [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) | | +| [IndexPatternsServiceProvider](./kibana-plugin-plugins-data-server.indexpatternsserviceprovider.md) | | | [OptionedParamType](./kibana-plugin-plugins-data-server.optionedparamtype.md) | | | [Plugin](./kibana-plugin-plugins-data-server.plugin.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 9dc38f96df4be6..f479ffd52e9b89 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -12,7 +12,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -31,7 +31,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }` diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index b57791db2b9fa5..464cc2b1f54d16 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -146,6 +146,8 @@ export { UI_SETTINGS, IndexPattern, IndexPatternLoadExpressionFunctionDefinition, + IndexPatternsService, + IndexPatternsService as IndexPatternsCommonService, } from '../common'; /** @@ -306,4 +308,4 @@ export const config: PluginConfigDescriptor = { schema: configSchema, }; -export type { IndexPatternsServiceProvider as IndexPatternsService } from './index_patterns'; +export type { IndexPatternsServiceProvider } from './index_patterns'; diff --git a/src/plugins/data/server/index_patterns/index_patterns_service.ts b/src/plugins/data/server/index_patterns/index_patterns_service.ts index 1888feb93ec0d1..5d703021b94da9 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_service.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_service.ts @@ -19,7 +19,7 @@ import { DataPluginStartDependencies, DataPluginStart } from '../plugin'; import { registerRoutes } from './routes'; import { indexPatternSavedObjectType } from '../saved_objects'; import { capabilitiesProvider } from './capabilities_provider'; -import { IndexPatternsService as IndexPatternsCommonService } from '../../common/index_patterns'; +import { IndexPatternsCommonService } from '../'; import { FieldFormatsStart } from '../field_formats'; import { getIndexPatternLoad } from './expressions'; import { UiSettingsServerToCommon } from './ui_settings_wrapper'; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 23aaab36e79055..c33bd155897802 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -889,11 +889,54 @@ export class IndexPatternsFetcher { validatePatternListActive(patternList: string[]): Promise; } +// Warning: (ae-missing-release-tag) "IndexPatternsService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +class IndexPatternsService { + // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceDeps" needs to be exported by the entry point index.d.ts + constructor({ uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, }: IndexPatternsServiceDeps); + clearCache: (id?: string | undefined) => void; + create(spec: IndexPatternSpec, skipFetchFields?: boolean): Promise; + createAndSave(spec: IndexPatternSpec, override?: boolean, skipFetchFields?: boolean): Promise; + createSavedObject(indexPattern: IndexPattern, override?: boolean): Promise; + delete(indexPatternId: string): Promise<{}>; + // Warning: (ae-forgotten-export) The symbol "EnsureDefaultIndexPattern" needs to be exported by the entry point index.d.ts + // + // (undocumented) + ensureDefaultIndexPattern: EnsureDefaultIndexPattern; + // Warning: (ae-forgotten-export) The symbol "FieldAttrs" needs to be exported by the entry point index.d.ts + fieldArrayToMap: (fields: FieldSpec[], fieldAttrs?: FieldAttrs | undefined) => Record; + find: (search: string, size?: number) => Promise; + get: (id: string) => Promise; + // Warning: (ae-forgotten-export) The symbol "IndexPatternSavedObjectAttrs" needs to be exported by the entry point index.d.ts + // + // (undocumented) + getCache: () => Promise[] | null | undefined>; + getDefault: () => Promise; + getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise; + // Warning: (ae-forgotten-export) The symbol "GetFieldsOptions" needs to be exported by the entry point index.d.ts + getFieldsForWildcard: (options: GetFieldsOptions) => Promise; + getIds: (refresh?: boolean) => Promise; + getIdsWithTitle: (refresh?: boolean) => Promise>; + getTitles: (refresh?: boolean) => Promise; + refreshFields: (indexPattern: IndexPattern) => Promise; + savedObjectToSpec: (savedObject: SavedObject_2) => IndexPatternSpec; + setDefault: (id: string, force?: boolean) => Promise; + updateSavedObject(indexPattern: IndexPattern, saveAttempts?: number, ignoreErrors?: boolean): Promise; +} + +export { IndexPatternsService as IndexPatternsCommonService } + +export { IndexPatternsService } + // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceStart" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "IndexPatternsServiceProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class IndexPatternsService implements Plugin_3 { +export class IndexPatternsServiceProvider implements Plugin_3 { // Warning: (ae-forgotten-export) The symbol "DataPluginStartDependencies" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceSetupDeps" needs to be exported by the entry point index.d.ts // @@ -903,7 +946,7 @@ export class IndexPatternsService implements Plugin_3 Promise; + indexPatternsServiceFactory: (savedObjectsClient: SavedObjectsClientContract_2, elasticsearchClient: ElasticsearchClient_2) => Promise; }; } @@ -1139,7 +1182,7 @@ export class Plugin implements Plugin_2 Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -1418,21 +1461,20 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:100:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:126:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:126:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:241:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:242:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:251:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:252:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:253:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:257:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:258:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:265:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index_patterns/index_patterns_service.ts:59:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:241:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:244:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:253:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:254:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:255:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:259:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:260:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:264:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:267:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:268:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:79:74 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts // src/plugins/data/server/search/types.ts:114:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts diff --git a/x-pack/plugins/maps/server/kibana_server_services.ts b/x-pack/plugins/maps/server/kibana_server_services.ts index 97c17dbe3b33c8..6b59b460ad2c9c 100644 --- a/x-pack/plugins/maps/server/kibana_server_services.ts +++ b/x-pack/plugins/maps/server/kibana_server_services.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { ISavedObjectsRepository } from 'kibana/server'; +import { ElasticsearchClient, ISavedObjectsRepository } from 'kibana/server'; +import { SavedObjectsClient } from '../../../../src/core/server'; +import { IndexPatternsCommonService } from '../../../../src/plugins/data/server'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { IndexPatternsServiceStart } from '../../../../src/plugins/data/server/index_patterns'; let internalRepository: ISavedObjectsRepository; export const setInternalRepository = ( @@ -14,3 +18,15 @@ export const setInternalRepository = ( internalRepository = createInternalRepository(); }; export const getInternalRepository = () => internalRepository; + +let indexPatternsService: IndexPatternsCommonService; +export const setIndexPatternsService = async ( + indexPatternsServiceFactory: IndexPatternsServiceStart['indexPatternsServiceFactory'], + elasticsearchClient: ElasticsearchClient +) => { + indexPatternsService = await indexPatternsServiceFactory( + new SavedObjectsClient(getInternalRepository()), + elasticsearchClient + ); +}; +export const getIndexPatternsService = () => indexPatternsService; diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.test.js b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.test.js index 7243dd84d6a853..8725e672ec3682 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.test.js +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.test.js @@ -6,13 +6,70 @@ */ import mapSavedObjects from './test_resources/sample_map_saved_objects.json'; -import indexPatternSavedObjects from './test_resources/sample_index_pattern_saved_objects'; import { buildMapsIndexPatternsTelemetry, buildMapsSavedObjectsTelemetry, getLayerLists, } from './maps_telemetry'; +jest.mock('../kibana_server_services', () => { + // Mocked for geo shape agg detection + const testAggIndexPatternId = '4a7f6010-0aed-11ea-9dd2-95afd7ad44d4'; + const testAggIndexPattern = { + id: testAggIndexPatternId, + fields: [ + { + name: 'geometry', + esTypes: ['geo_shape'], + }, + ], + }; + const testIndexPatterns = { + 1: { + id: '1', + fields: [ + { + name: 'one', + esTypes: ['geo_point'], + }, + ], + }, + 2: { + id: '2', + fields: [ + { + name: 'two', + esTypes: ['geo_point'], + }, + ], + }, + 3: { + id: '3', + fields: [ + { + name: 'three', + esTypes: ['geo_shape'], + }, + ], + }, + }; + return { + getIndexPatternsService() { + return { + async get(x) { + return x === testAggIndexPatternId ? testAggIndexPattern : testIndexPatterns[x]; + }, + async getIds() { + return Object.values(testIndexPatterns).map((x) => x.id); + }, + async getFieldsForIndexPattern(x) { + return x.fields; + }, + }; + }, + }; +}); + describe('buildMapsSavedObjectsTelemetry', () => { test('returns zeroed telemetry data when there are no saved objects', async () => { const result = buildMapsSavedObjectsTelemetry([]); @@ -88,7 +145,7 @@ describe('buildMapsSavedObjectsTelemetry', () => { test('returns expected telemetry data from index patterns', async () => { const layerLists = getLayerLists(mapSavedObjects); - const result = buildMapsIndexPatternsTelemetry(indexPatternSavedObjects, layerLists); + const result = await buildMapsIndexPatternsTelemetry(layerLists); expect(result).toMatchObject({ indexPatternsWithGeoFieldCount: 3, diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts index 5b098af760e655..0387d96046cb11 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts @@ -7,7 +7,7 @@ import _ from 'lodash'; import { SavedObject } from 'kibana/server'; -import { IFieldType, IndexPatternAttributes } from 'src/plugins/data/public'; +import { IFieldType } from 'src/plugins/data/public'; import { ES_GEO_FIELD_TYPE, LAYER_TYPE, @@ -22,7 +22,7 @@ import { LayerDescriptor, } from '../../common/descriptor_types'; import { MapSavedObject, MapSavedObjectAttributes } from '../../common/map_saved_object_type'; -import { getInternalRepository } from '../kibana_server_services'; +import { getIndexPatternsService, getInternalRepository } from '../kibana_server_services'; import { MapsConfigType } from '../../config'; interface Settings { @@ -94,37 +94,6 @@ function getUniqueLayerCounts(layerCountsList: ILayerTypeCount[], mapsCount: num }, {}); } -function getIndexPatternsWithGeoFieldCount( - indexPatterns: Array> -) { - const fieldLists = indexPatterns.map((indexPattern) => - indexPattern.attributes && indexPattern.attributes.fields - ? JSON.parse(indexPattern.attributes.fields) - : [] - ); - - const fieldListsWithGeoFields = fieldLists.filter((fields) => - fields.some( - (field: IFieldType) => - field.type === ES_GEO_FIELD_TYPE.GEO_POINT || field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE - ) - ); - - const fieldListsWithGeoPointFields = fieldLists.filter((fields) => - fields.some((field: IFieldType) => field.type === ES_GEO_FIELD_TYPE.GEO_POINT) - ); - - const fieldListsWithGeoShapeFields = fieldLists.filter((fields) => - fields.some((field: IFieldType) => field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE) - ); - - return { - indexPatternsWithGeoFieldCount: fieldListsWithGeoFields.length, - indexPatternsWithGeoPointFieldCount: fieldListsWithGeoPointFields.length, - indexPatternsWithGeoShapeFieldCount: fieldListsWithGeoShapeFields.length, - }; -} - function getEMSLayerCount(layerLists: LayerDescriptor[][]): ILayerTypeCount[] { return layerLists.map((layerList: LayerDescriptor[]) => { const emsLayers = layerList.filter((layer: LayerDescriptor) => { @@ -143,41 +112,25 @@ function getEMSLayerCount(layerLists: LayerDescriptor[][]): ILayerTypeCount[] { }) as ILayerTypeCount[]; } -function isFieldGeoShape( - indexPatterns: Array>, +async function isFieldGeoShape( indexPatternId: string, geoField: string | undefined -): boolean { - if (!geoField) { +): Promise { + if (!geoField || !indexPatternId) { return false; } - - const matchIndexPattern = indexPatterns.find( - (indexPattern: SavedObject) => { - return indexPattern.id === indexPatternId; - } - ); - - if (!matchIndexPattern) { + const indexPatternsService = await getIndexPatternsService(); + const indexPattern = await indexPatternsService.get(indexPatternId); + if (!indexPattern) { return false; } - - const fieldList: IFieldType[] = - matchIndexPattern.attributes && matchIndexPattern.attributes.fields - ? JSON.parse(matchIndexPattern.attributes.fields) - : []; - - const matchField = fieldList.find((field: IFieldType) => { - return field.name === geoField; - }); - - return !!matchField && matchField.type === ES_GEO_FIELD_TYPE.GEO_SHAPE; + const fieldsForIndexPattern = await indexPatternsService.getFieldsForIndexPattern(indexPattern); + return fieldsForIndexPattern.some( + (fieldDescriptor: IFieldType) => fieldDescriptor.name && fieldDescriptor.name === geoField! + ); } -function isGeoShapeAggLayer( - indexPatterns: Array>, - layer: LayerDescriptor -): boolean { +async function isGeoShapeAggLayer(layer: LayerDescriptor): Promise { if (layer.sourceDescriptor === null) { return false; } @@ -192,8 +145,7 @@ function isGeoShapeAggLayer( const sourceDescriptor = layer.sourceDescriptor; if (sourceDescriptor.type === SOURCE_TYPES.ES_GEO_GRID) { - return isFieldGeoShape( - indexPatterns, + return await isFieldGeoShape( (sourceDescriptor as ESGeoGridSourceDescriptor).indexPatternId, (sourceDescriptor as ESGeoGridSourceDescriptor).geoField ); @@ -201,8 +153,7 @@ function isGeoShapeAggLayer( sourceDescriptor.type === SOURCE_TYPES.ES_SEARCH && (sourceDescriptor as ESSearchSourceDescriptor).scalingType === SCALING_TYPES.CLUSTERS ) { - return isFieldGeoShape( - indexPatterns, + return await isFieldGeoShape( (sourceDescriptor as ESSearchSourceDescriptor).indexPatternId, (sourceDescriptor as ESSearchSourceDescriptor).geoField ); @@ -211,17 +162,15 @@ function isGeoShapeAggLayer( } } -function getGeoShapeAggCount( - layerLists: LayerDescriptor[][], - indexPatterns: Array> -): number { - const countsPerMap: number[] = layerLists.map((layerList: LayerDescriptor[]) => { - const geoShapeAggLayers = layerList.filter((layerDescriptor) => { - return isGeoShapeAggLayer(indexPatterns, layerDescriptor); - }); - return geoShapeAggLayers.length; - }); - +async function getGeoShapeAggCount(layerLists: LayerDescriptor[][]): Promise { + const countsPerMap: number[] = await Promise.all( + layerLists.map(async (layerList: LayerDescriptor[]) => { + const boolIsAggLayerArr = await Promise.all( + layerList.map(async (layerDescriptor) => await isGeoShapeAggLayer(layerDescriptor)) + ); + return boolIsAggLayerArr.filter((x) => x).length; + }) + ); return _.sum(countsPerMap); } @@ -235,30 +184,56 @@ export function getLayerLists(mapSavedObjects: MapSavedObject[]): LayerDescripto }); } -export function buildMapsIndexPatternsTelemetry( - indexPatternSavedObjects: Array>, - layerLists: LayerDescriptor[][] -): GeoIndexPatternsUsage { - const { - indexPatternsWithGeoFieldCount, - indexPatternsWithGeoPointFieldCount, - indexPatternsWithGeoShapeFieldCount, - } = getIndexPatternsWithGeoFieldCount(indexPatternSavedObjects); +async function filterIndexPatternsByField(fields: string[]) { + const indexPatternsService = await getIndexPatternsService(); + const indexPatternIds = await indexPatternsService.getIds(true); + let numIndexPatternsContainingField = 0; + await Promise.all( + indexPatternIds.map(async (indexPatternId: string) => { + const indexPattern = await indexPatternsService.get(indexPatternId); + const fieldsForIndexPattern = await indexPatternsService.getFieldsForIndexPattern( + indexPattern + ); + const containsField = fields.some((field: string) => + fieldsForIndexPattern.some( + (fieldDescriptor: IFieldType) => + fieldDescriptor.esTypes && fieldDescriptor.esTypes.includes(field) + ) + ); + if (containsField) { + numIndexPatternsContainingField++; + } + }) + ); + return numIndexPatternsContainingField; +} +export async function buildMapsIndexPatternsTelemetry( + layerLists: LayerDescriptor[][] +): Promise { + const indexPatternsWithGeoField = await filterIndexPatternsByField([ + ES_GEO_FIELD_TYPE.GEO_POINT, + ES_GEO_FIELD_TYPE.GEO_SHAPE, + ]); + const indexPatternsWithGeoPointField = await filterIndexPatternsByField([ + ES_GEO_FIELD_TYPE.GEO_POINT, + ]); + const indexPatternsWithGeoShapeField = await filterIndexPatternsByField([ + ES_GEO_FIELD_TYPE.GEO_SHAPE, + ]); // Tracks whether user uses Gold+ only functionality - const geoShapeAggLayersCount = getGeoShapeAggCount(layerLists, indexPatternSavedObjects); + const geoShapeAggLayersCount = await getGeoShapeAggCount(layerLists); return { - indexPatternsWithGeoFieldCount, - indexPatternsWithGeoPointFieldCount, - indexPatternsWithGeoShapeFieldCount, + indexPatternsWithGeoFieldCount: indexPatternsWithGeoField, + indexPatternsWithGeoPointFieldCount: indexPatternsWithGeoPointField, + indexPatternsWithGeoShapeFieldCount: indexPatternsWithGeoShapeField, geoShapeAggLayersCount, }; } export function buildMapsSavedObjectsTelemetry(layerLists: LayerDescriptor[][]): LayersStatsUsage { const mapsCount = layerLists.length; - const dataSourcesCount = layerLists.map((layerList: LayerDescriptor[]) => { // todo: not every source-descriptor has an id // @ts-ignore @@ -340,16 +315,7 @@ export async function getMapsTelemetry(config: MapsConfigType): Promise( - 'index-pattern', - (savedObjects) => - _.mergeWith( - indexPatternsTelemetry, - buildMapsIndexPatternsTelemetry(savedObjects, layerLists), - (prevVal, currVal) => prevVal || 0 + currVal || 0 // Additive merge - ) - ); + const indexPatternsTelemetry = await buildMapsIndexPatternsTelemetry(layerLists); return { settings: { diff --git a/x-pack/plugins/maps/server/maps_telemetry/test_resources/sample_index_pattern_saved_objects.json b/x-pack/plugins/maps/server/maps_telemetry/test_resources/sample_index_pattern_saved_objects.json deleted file mode 100644 index 0b36d5ff840162..00000000000000 --- a/x-pack/plugins/maps/server/maps_telemetry/test_resources/sample_index_pattern_saved_objects.json +++ /dev/null @@ -1,59 +0,0 @@ -[ - { - "attributes": { - "fields": "[{\"name\":\"geometry\",\"type\":\"geo_shape\",\"esTypes\":[\"geo_shape\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false}]", - "timeFieldName": "ORIG_DATE", - "title": "indexpattern-with-geoshape" - }, - "id": "4a7f6010-0aed-11ea-9dd2-95afd7ad44d4", - "migrationVersion": { - "index-pattern": "7.6.0" - }, - "references": [], - "type": "index-pattern", - "updated_at": "2019-11-19T16:54:46.405Z", - "version": "Wzg0LDFd" - }, - { - "attributes": { - "fields": "[{\"name\":\"geometry\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", - "title": "indexpattern-with-geopoint" - }, - "id": "55d572f0-0b07-11ea-9dd2-95afd7ad44d4", - "migrationVersion": { - "index-pattern": "7.6.0" - }, - "references": [], - "type": "index-pattern", - "updated_at": "2019-11-19T20:05:37.607Z", - "version": "WzExMSwxXQ==" - }, - { - "attributes": { - "fields": "[{\"name\":\"geometry\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", - "title": "indexpattern-with-geopoint2" - }, - "id": "55d572f0-0b07-11ea-9dd2-95afd7ad44d4", - "migrationVersion": { - "index-pattern": "7.6.0" - }, - "references": [], - "type": "index-pattern", - "updated_at": "2019-11-19T20:05:37.607Z", - "version": "WzExMSwxXQ==" - }, - { - "attributes": { - "fields": "[{\"name\":\"assessment_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"date_exterior_condition\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"recording_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sale_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", - "title": "indexpattern-without-geo" - }, - "id": "55d572f0-0b07-11ea-9dd2-95afd7ad44d4", - "migrationVersion": { - "index-pattern": "7.6.0" - }, - "references": [], - "type": "index-pattern", - "updated_at": "2019-11-19T20:05:37.607Z", - "version": "WzExMSwxXQ==" - } -] diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts index cb22a98b70aa80..4118074841aef2 100644 --- a/x-pack/plugins/maps/server/plugin.ts +++ b/x-pack/plugins/maps/server/plugin.ts @@ -20,7 +20,7 @@ import { APP_ID, APP_ICON, MAP_SAVED_OBJECT_TYPE, getExistingMapPath } from '../ import { mapSavedObjects, mapsTelemetrySavedObjects } from './saved_objects'; import { MapsXPackConfig } from '../config'; // @ts-ignore -import { setInternalRepository } from './kibana_server_services'; +import { setIndexPatternsService, setInternalRepository } from './kibana_server_services'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; import { emsBoundariesSpecProvider } from './tutorials/ems'; // @ts-ignore @@ -30,6 +30,7 @@ import { LicensingPluginSetup } from '../../licensing/server'; import { HomeServerPluginSetup } from '../../../../src/plugins/home/server'; import { MapsLegacyPluginSetup } from '../../../../src/plugins/maps_legacy/server'; import { EMSSettings } from '../common/ems_settings'; +import { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server'; interface SetupDeps { features: FeaturesPluginSetupContract; @@ -39,6 +40,10 @@ interface SetupDeps { mapsLegacy: MapsLegacyPluginSetup; } +export interface StartDeps { + data: DataPluginStart; +} + export class MapsPlugin implements Plugin { readonly _initializerContext: PluginInitializerContext; private readonly _logger: Logger; @@ -208,7 +213,11 @@ export class MapsPlugin implements Plugin { } // @ts-ignore - start(core: CoreStart) { + start(core: CoreStart, plugins: StartDeps) { setInternalRepository(core.savedObjects.createInternalRepository); + setIndexPatternsService( + plugins.data.indexPatterns.indexPatternsServiceFactory, + core.elasticsearch.client.asInternalUser + ); } } From a108469ec78a6df4d7628eb94f492b583e7014e6 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Fri, 19 Feb 2021 10:50:35 -0500 Subject: [PATCH 77/93] Allowing deletion of collections (#91926) --- .../server/routes/api/cases/delete_cases.ts | 44 ------------ .../basic/tests/cases/delete_cases.ts | 71 ++++++++++++++++++- 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts b/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts index 263b814df4146e..497e33d7feb308 100644 --- a/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/delete_cases.ts @@ -8,43 +8,12 @@ import { schema } from '@kbn/config-schema'; import { SavedObjectsClientContract } from 'src/core/server'; -import { CaseType } from '../../../../common/api'; import { buildCaseUserActionItem } from '../../../services/user_actions/helpers'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; import { CASES_URL } from '../../../../common/constants'; import { CaseServiceSetup } from '../../../services'; -async function unremovableCases({ - caseService, - client, - ids, - force, -}: { - caseService: CaseServiceSetup; - client: SavedObjectsClientContract; - ids: string[]; - force: boolean | undefined; -}): Promise { - // if the force flag was included then we can skip checking whether the cases are collections and go ahead - // and delete them - if (force) { - return []; - } - - const cases = await caseService.getCases({ caseIds: ids, client }); - const parentCases = cases.saved_objects.filter( - /** - * getCases will return an array of saved_objects and some can be successful cases where as others - * might have failed to find the ID. If it fails to find it, it will set the error field but not - * the attributes so check that we didn't receive an error. - */ - (caseObj) => !caseObj.error && caseObj.attributes.type === CaseType.collection - ); - - return parentCases.map((parentCase) => parentCase.id); -} - async function deleteSubCases({ caseService, client, @@ -84,25 +53,12 @@ export function initDeleteCasesApi({ caseService, router, userActionService }: R validate: { query: schema.object({ ids: schema.arrayOf(schema.string()), - force: schema.maybe(schema.boolean()), }), }, }, async (context, request, response) => { try { const client = context.core.savedObjects.client; - const unremovable = await unremovableCases({ - caseService, - client, - ids: request.query.ids, - force: request.query.force, - }); - - if (unremovable.length > 0) { - return response.badRequest({ - body: `Case IDs: [${unremovable.join(' ,')}] are not removable`, - }); - } await Promise.all( request.query.ids.map((id) => caseService.deleteCase({ diff --git a/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts b/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts index 1ea4712e260f0e..8edc3b0d081138 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/delete_cases.ts @@ -10,7 +10,17 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { CASES_URL } from '../../../../../plugins/case/common/constants'; import { postCaseReq, postCommentUserReq } from '../../../common/lib/mock'; -import { deleteCases, deleteCasesUserActions, deleteComments } from '../../../common/lib/utils'; +import { + createCaseAction, + createSubCase, + deleteAllCaseItems, + deleteCaseAction, + deleteCases, + deleteCasesUserActions, + deleteComments, +} from '../../../common/lib/utils'; +import { getSubCaseDetailsUrl } from '../../../../../plugins/case/common/api/helpers'; +import { CollectionWithSubCaseResponse } from '../../../../../plugins/case/common/api'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -79,5 +89,64 @@ export default ({ getService }: FtrProviderContext): void => { .send() .expect(404); }); + + describe('sub cases', () => { + let actionID: string; + before(async () => { + actionID = await createCaseAction(supertest); + }); + after(async () => { + await deleteCaseAction(supertest, actionID); + }); + afterEach(async () => { + await deleteAllCaseItems(es); + }); + + it('should delete the sub cases when deleting a collection', async () => { + const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID }); + expect(caseInfo.subCase?.id).to.not.eql(undefined); + + const { body } = await supertest + .delete(`${CASES_URL}?ids=["${caseInfo.id}"]`) + .set('kbn-xsrf', 'true') + .send() + .expect(204); + + expect(body).to.eql({}); + await supertest + .get(getSubCaseDetailsUrl(caseInfo.id, caseInfo.subCase!.id)) + .send() + .expect(404); + }); + + it(`should delete a sub case's comments when that case gets deleted`, async () => { + const { newSubCaseInfo: caseInfo } = await createSubCase({ supertest, actionID }); + expect(caseInfo.subCase?.id).to.not.eql(undefined); + + // there should be two comments on the sub case now + const { + body: patchedCaseWithSubCase, + }: { body: CollectionWithSubCaseResponse } = await supertest + .post(`${CASES_URL}/${caseInfo.id}/comments`) + .set('kbn-xsrf', 'true') + .query({ subCaseID: caseInfo.subCase!.id }) + .send(postCommentUserReq) + .expect(200); + + const subCaseCommentUrl = `${CASES_URL}/${patchedCaseWithSubCase.id}/comments/${ + patchedCaseWithSubCase.subCase!.comments![1].id + }`; + // make sure we can get the second comment + await supertest.get(subCaseCommentUrl).set('kbn-xsrf', 'true').send().expect(200); + + await supertest + .delete(`${CASES_URL}?ids=["${caseInfo.id}"]`) + .set('kbn-xsrf', 'true') + .send() + .expect(204); + + await supertest.get(subCaseCommentUrl).set('kbn-xsrf', 'true').send().expect(404); + }); + }); }); }; From d6984ca160fe40b615e2fd813d8318451b09d9a5 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Fri, 19 Feb 2021 10:56:52 -0500 Subject: [PATCH 78/93] [Maps] upgrade mapbox-gl to v1.13.1 (#91564) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a65c12fce46990..67263f53f28a24 100644 --- a/package.json +++ b/package.json @@ -724,7 +724,7 @@ "loader-utils": "^1.2.3", "log-symbols": "^2.2.0", "lz-string": "^1.4.4", - "mapbox-gl": "^1.12.0", + "mapbox-gl": "1.13.1", "mapbox-gl-draw-rectangle-mode": "^1.0.4", "marge": "^1.0.1", "memoize-one": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index 4738925e44dfbc..8a8147bd25aef2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20250,10 +20250,10 @@ mapbox-gl-draw-rectangle-mode@^1.0.4: resolved "https://registry.yarnpkg.com/mapbox-gl-draw-rectangle-mode/-/mapbox-gl-draw-rectangle-mode-1.0.4.tgz#42987d68872a5fb5cc5d76d3375ee20cd8bab8f7" integrity sha512-BdF6nwEK2p8n9LQoMPzBO8LhddW1fe+d5vK8HQIei+4VcRnUbKNsEj7Z15FsJxCHzsc2BQKXbESx5GaE8x0imQ== -mapbox-gl@^1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-1.12.0.tgz#7d1c73b1153d7ee219d30d80728d7df079bc7c05" - integrity sha512-B3URR4qY9R/Bx+DKqP8qmGCai8IOZYMSZF7ZSvcCZaYTaOYhQQi8ErTEDZtFMOR0ZPj7HFWOkkhl5SqvDfpJpA== +mapbox-gl@1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-1.13.1.tgz#322efe75ab4c764fc4c776da1506aad58d5a5b9d" + integrity sha512-GSyubcoSF5MyaP8z+DasLu5v7KmDK2pp4S5+VQ5WdVQUOaAqQY4jwl4JpcdNho3uWm2bIKs7x1l7q3ynGmW60g== dependencies: "@mapbox/geojson-rewind" "^0.5.0" "@mapbox/geojson-types" "^1.0.2" From 99a60caf81ea043b1e0e67d678a2ea03fd58b060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Fri, 19 Feb 2021 11:15:40 -0500 Subject: [PATCH 79/93] [APM] Bug: Service overview - Sparkline loading state icons has changed (#91884) * adjusting icon size * Updating color --- .../components/shared/charts/spark_plot/index.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx index 36c499f9e5ee40..a89d36f7089900 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx @@ -68,7 +68,16 @@ export function SparkPlot({ {!series || series.every((point) => point.y === null) ? ( - +
    + +
    ) : ( Date: Fri, 19 Feb 2021 16:41:00 +0000 Subject: [PATCH 80/93] [Discover] Always show unmapped fields (#91735) * [Discover] Always show unmapped fields * Updating the functional test * Remove unmapped switch toggle * Some more code cleanup Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/application/angular/discover.js | 11 +-- .../sidebar/discover_field_search.test.tsx | 18 ----- .../sidebar/discover_field_search.tsx | 77 +++---------------- .../components/sidebar/discover_sidebar.tsx | 2 - .../discover_sidebar_responsive.test.tsx | 2 - .../sidebar/discover_sidebar_responsive.tsx | 13 +--- .../public/application/components/types.ts | 13 +--- .../embeddable/search_embeddable.ts | 7 +- .../public/saved_searches/_saved_search.ts | 3 - .../discover/public/saved_searches/types.ts | 1 - .../discover/server/saved_objects/search.ts | 1 - .../saved_objects/search_migrations.test.ts | 37 --------- .../server/saved_objects/search_migrations.ts | 19 ----- .../_indexpattern_with_unmapped_fields.ts | 29 +------ 14 files changed, 17 insertions(+), 216 deletions(-) diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 78ad40e48fd965..88747cf9e84d8c 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -675,19 +675,10 @@ function discoverController($route, $scope, Promise) { history.push('/'); }; - const showUnmappedFieldsDefaultValue = $scope.useNewFieldsApi && !!$scope.opts.savedSearch.pre712; - let showUnmappedFields = showUnmappedFieldsDefaultValue; - - const onChangeUnmappedFields = (value) => { - showUnmappedFields = value; - $scope.unmappedFieldsConfig.showUnmappedFields = value; - $scope.fetch(); - }; + const showUnmappedFields = $scope.useNewFieldsApi; $scope.unmappedFieldsConfig = { - showUnmappedFieldsDefaultValue, showUnmappedFields, - onChangeUnmappedFields, }; $scope.updateDataSource = () => { diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx index 797a6c9697c351..04562cbd26520b 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.test.tsx @@ -136,22 +136,4 @@ describe('DiscoverFieldSearch', () => { popover = component.find(EuiPopover); expect(popover.prop('isOpen')).toBe(false); }); - - test('unmapped fields', () => { - const onChangeUnmappedFields = jest.fn(); - const componentProps = { - ...defaultProps, - showUnmappedFields: true, - useNewFieldsApi: false, - onChangeUnmappedFields, - }; - const component = mountComponent(componentProps); - const btn = findTestSubject(component, 'toggleFieldFilterButton'); - btn.simulate('click'); - const unmappedFieldsSwitch = findTestSubject(component, 'unmappedFieldsSwitch'); - act(() => { - unmappedFieldsSwitch.simulate('click'); - }); - expect(onChangeUnmappedFields).toHaveBeenCalledWith(false); - }); }); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx index 8fb90bfea3a950..1e99959d77134f 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx @@ -27,8 +27,6 @@ import { EuiOutsideClickDetector, EuiFilterButton, EuiSpacer, - EuiIcon, - EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -37,7 +35,6 @@ export interface State { aggregatable: string; type: string; missing: boolean; - unmappedFields: boolean; [index: string]: string | boolean; } @@ -61,31 +58,13 @@ export interface Props { * use new fields api */ useNewFieldsApi?: boolean; - - /** - * callback funtion to change the value of unmapped fields switch - * @param value new value to set - */ - onChangeUnmappedFields?: (value: boolean) => void; - - /** - * should unmapped fields switch be rendered - */ - showUnmappedFields?: boolean; } /** * Component is Discover's side bar to search of available fields * Additionally there's a button displayed that allows the user to show/hide more filter fields */ -export function DiscoverFieldSearch({ - onChange, - value, - types, - useNewFieldsApi, - showUnmappedFields, - onChangeUnmappedFields, -}: Props) { +export function DiscoverFieldSearch({ onChange, value, types, useNewFieldsApi }: Props) { const searchPlaceholder = i18n.translate('discover.fieldChooser.searchPlaceHolder', { defaultMessage: 'Search field names', }); @@ -111,7 +90,6 @@ export function DiscoverFieldSearch({ aggregatable: 'any', type: 'any', missing: true, - unmappedFields: !!showUnmappedFields, }); if (typeof value !== 'string') { @@ -181,14 +159,6 @@ export function DiscoverFieldSearch({ handleValueChange('missing', missingValue); }; - const handleUnmappedFieldsChange = (e: EuiSwitchEvent) => { - const unmappedFieldsValue = e.target.checked; - handleValueChange('unmappedFields', unmappedFieldsValue); - if (onChangeUnmappedFields) { - onChangeUnmappedFields(unmappedFieldsValue); - } - }; - const buttonContent = ( { - if (!showUnmappedFields && useNewFieldsApi) { + if (useNewFieldsApi) { return null; } return ( - {showUnmappedFields ? ( - - - - - - - - - - - ) : null} - {useNewFieldsApi ? null : ( - - )} + ); }; diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx index f0303553dfac0a..c0a192550e6c4a 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -205,8 +205,6 @@ export function DiscoverSidebar({ value={fieldFilter.name} types={fieldTypes} useNewFieldsApi={useNewFieldsApi} - onChangeUnmappedFields={unmappedFieldsConfig?.onChangeUnmappedFields} - showUnmappedFields={unmappedFieldsConfig?.showUnmappedFieldsDefaultValue} />
    diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx index 7b12ab5f9bcd9e..79e8caabd49307 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx @@ -137,9 +137,7 @@ describe('discover responsive sidebar', function () { }); it('renders sidebar with unmapped fields config', function () { const unmappedFieldsConfig = { - onChangeUnmappedFields: jest.fn(), showUnmappedFields: false, - showUnmappedFieldsDefaultValue: false, }; const componentProps = { ...props, unmappedFieldsConfig }; const component = mountWithIntl(); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx index b689db12969222..f0e7c71f9c970d 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx @@ -113,24 +113,13 @@ export interface DiscoverSidebarResponsiveProps { useNewFieldsApi?: boolean; /** - * an object containing properties for proper handling of unmapped fields in the UI + * an object containing properties for proper handling of unmapped fields */ unmappedFieldsConfig?: { - /** - * callback function to change the value of `showUnmappedFields` flag - * @param value new value to set - */ - onChangeUnmappedFields: (value: boolean) => void; /** * determines whether to display unmapped fields - * configurable through the switch in the UI */ showUnmappedFields: boolean; - /** - * determines if we should display an option to toggle showUnmappedFields value in the first place - * this value is not configurable through the UI - */ - showUnmappedFieldsDefaultValue: boolean; }; } diff --git a/src/plugins/discover/public/application/components/types.ts b/src/plugins/discover/public/application/components/types.ts index e276795f9ed7fd..e488f596cece85 100644 --- a/src/plugins/discover/public/application/components/types.ts +++ b/src/plugins/discover/public/application/components/types.ts @@ -159,23 +159,12 @@ export interface DiscoverProps { */ timeRange?: { from: string; to: string }; /** - * An object containing properties for proper handling of unmapped fields in the UI + * An object containing properties for unmapped fields behavior */ unmappedFieldsConfig?: { /** * determines whether to display unmapped fields - * configurable through the switch in the UI */ showUnmappedFields: boolean; - /** - * determines if we should display an option to toggle showUnmappedFields value in the first place - * this value is not configurable through the UI - */ - showUnmappedFieldsDefaultValue: boolean; - /** - * callback function to change the value of `showUnmappedFields` flag - * @param value new value to set - */ - onChangeUnmappedFields: (value: boolean) => void; }; } diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index 658734aa46cb02..2bafa239075027 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -291,7 +291,7 @@ export class SearchEmbeddable const useNewFieldsApi = !this.services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE, false); if (!this.searchScope) return; - const { searchSource, pre712 } = this.savedSearch; + const { searchSource } = this.savedSearch; // Abort any in-progress requests if (this.abortController) this.abortController.abort(); @@ -308,10 +308,7 @@ export class SearchEmbeddable ); if (useNewFieldsApi) { searchSource.removeField('fieldsFromSource'); - const fields: Record = { field: '*' }; - if (pre712) { - fields.include_unmapped = 'true'; - } + const fields: Record = { field: '*', include_unmapped: 'true' }; searchSource.setField('fields', [fields]); } else { searchSource.removeField('fields'); diff --git a/src/plugins/discover/public/saved_searches/_saved_search.ts b/src/plugins/discover/public/saved_searches/_saved_search.ts index a7b6ef49cacd21..320332ca4ace54 100644 --- a/src/plugins/discover/public/saved_searches/_saved_search.ts +++ b/src/plugins/discover/public/saved_searches/_saved_search.ts @@ -20,7 +20,6 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) { grid: 'object', sort: 'keyword', version: 'integer', - pre712: 'boolean', }; // Order these fields to the top, the rest are alphabetical public static fieldOrder = ['title', 'description']; @@ -42,7 +41,6 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) { grid: 'object', sort: 'keyword', version: 'integer', - pre712: 'boolean', }, searchSource: true, defaults: { @@ -52,7 +50,6 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) { hits: 0, sort: [], version: 1, - pre712: false, }, }); this.showInRecentlyAccessed = true; diff --git a/src/plugins/discover/public/saved_searches/types.ts b/src/plugins/discover/public/saved_searches/types.ts index 4646744ee0ef3c..b1c7b48d696b34 100644 --- a/src/plugins/discover/public/saved_searches/types.ts +++ b/src/plugins/discover/public/saved_searches/types.ts @@ -23,7 +23,6 @@ export interface SavedSearch { save: (saveOptions: SavedObjectSaveOpts) => Promise; lastSavedTitle?: string; copyOnSave?: boolean; - pre712?: boolean; hideChart?: boolean; } export interface SavedSearchLoader { diff --git a/src/plugins/discover/server/saved_objects/search.ts b/src/plugins/discover/server/saved_objects/search.ts index de3a2197fe0acc..b66c06db3e1200 100644 --- a/src/plugins/discover/server/saved_objects/search.ts +++ b/src/plugins/discover/server/saved_objects/search.ts @@ -45,7 +45,6 @@ export const searchSavedObjectType: SavedObjectsType = { title: { type: 'text' }, grid: { type: 'object', enabled: false }, version: { type: 'integer' }, - pre712: { type: 'boolean' }, }, }, migrations: searchMigrations as any, diff --git a/src/plugins/discover/server/saved_objects/search_migrations.test.ts b/src/plugins/discover/server/saved_objects/search_migrations.test.ts index f1dc228a9ac082..fb608c0b6f3e81 100644 --- a/src/plugins/discover/server/saved_objects/search_migrations.test.ts +++ b/src/plugins/discover/server/saved_objects/search_migrations.test.ts @@ -350,41 +350,4 @@ Object { testMigrateMatchAllQuery(migrationFn); }); }); - - describe('7.12.0', () => { - const migrationFn = searchMigrations['7.12.0']; - - describe('migrateExistingSavedSearch', () => { - it('should add a new flag to existing saved searches', () => { - const migratedDoc = migrationFn( - { - type: 'search', - attributes: { - kibanaSavedObjectMeta: {}, - }, - }, - savedObjectMigrationContext - ); - const migratedPre712Flag = migratedDoc.attributes.pre712; - - expect(migratedPre712Flag).toEqual(true); - }); - - it('should not modify a flag if it already exists', () => { - const migratedDoc = migrationFn( - { - type: 'search', - attributes: { - kibanaSavedObjectMeta: {}, - pre712: false, - }, - }, - savedObjectMigrationContext - ); - const migratedPre712Flag = migratedDoc.attributes.pre712; - - expect(migratedPre712Flag).toEqual(false); - }); - }); - }); }); diff --git a/src/plugins/discover/server/saved_objects/search_migrations.ts b/src/plugins/discover/server/saved_objects/search_migrations.ts index 72749bfd2e9cdd..feaf91409797a2 100644 --- a/src/plugins/discover/server/saved_objects/search_migrations.ts +++ b/src/plugins/discover/server/saved_objects/search_migrations.ts @@ -117,28 +117,9 @@ const migrateSearchSortToNestedArray: SavedObjectMigrationFn = (doc) = }; }; -const migrateExistingSavedSearch: SavedObjectMigrationFn = (doc) => { - if (!doc.attributes) { - return doc; - } - const pre712 = doc.attributes.pre712; - // pre712 already has a value - if (pre712 !== undefined) { - return doc; - } - return { - ...doc, - attributes: { - ...doc.attributes, - pre712: true, - }, - }; -}; - export const searchMigrations = { '6.7.2': flow(migrateMatchAllQuery), '7.0.0': flow(setNewReferences), '7.4.0': flow(migrateSearchSortToNestedArray), '7.9.3': flow(migrateMatchAllQuery), - '7.12.0': flow(migrateExistingSavedSearch), }; diff --git a/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts b/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts index 0990b3fa29f708..06933e828db7e1 100644 --- a/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts +++ b/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts @@ -12,14 +12,11 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); - const testSubjects = getService('testSubjects'); const log = getService('log'); const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'timePicker', 'discover']); describe('index pattern with unmapped fields', () => { - const unmappedFieldsSwitchSelector = 'unmappedFieldsSwitch'; - before(async () => { await esArchiver.loadIfNeeded('unmapped_fields'); await kibanaServer.uiSettings.replace({ @@ -37,7 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await esArchiver.unload('unmapped_fields'); }); - it('unmapped fields do not exist on a new saved search', async () => { + it('unmapped fields exist on a new saved search', async () => { const expectedHitCount = '4'; await retry.try(async function () { expect(await PageObjects.discover.getHitCount()).to.be(expectedHitCount); @@ -46,13 +43,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // message is a mapped field expect(allFields.includes('message')).to.be(true); // sender is not a mapped field - expect(allFields.includes('sender')).to.be(false); - }); - - it('unmapped fields toggle does not exist on a new saved search', async () => { - await PageObjects.discover.openSidebarFieldFilter(); - await testSubjects.existOrFail('filterSelectionPanel'); - await testSubjects.missingOrFail('unmappedFieldsSwitch'); + expect(allFields.includes('sender')).to.be(true); }); it('unmapped fields exist on an existing saved search', async () => { @@ -66,21 +57,5 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(allFields.includes('sender')).to.be(true); expect(allFields.includes('receiver')).to.be(true); }); - - it('unmapped fields toggle exists on an existing saved search', async () => { - await PageObjects.discover.openSidebarFieldFilter(); - await testSubjects.existOrFail('filterSelectionPanel'); - await testSubjects.existOrFail(unmappedFieldsSwitchSelector); - expect(await testSubjects.isEuiSwitchChecked(unmappedFieldsSwitchSelector)).to.be(true); - }); - - it('switching unmapped fields toggle off hides unmapped fields', async () => { - await testSubjects.setEuiSwitch(unmappedFieldsSwitchSelector, 'uncheck'); - await PageObjects.discover.closeSidebarFieldFilter(); - const allFields = await PageObjects.discover.getAllFieldNames(); - expect(allFields.includes('message')).to.be(true); - expect(allFields.includes('sender')).to.be(false); - expect(allFields.includes('receiver')).to.be(false); - }); }); } From 10b1fddf356f2d871aee5bd195d76245370e1af0 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Fri, 19 Feb 2021 08:49:48 -0800 Subject: [PATCH 81/93] [Fleet] Handle long text in agent details page (#91776) * Fix #85521 * Set a minimum height for agent logs component #89831 * Truncate long integration names nicely #85404 --- .../agent_details/agent_details_integrations.tsx | 14 +++++++++----- .../agent_details/agent_details_overview.tsx | 4 +++- .../components/agent_logs/agent_logs.tsx | 9 +++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx index d8beabab67ef14..d71fb8be5f9cf4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx @@ -27,8 +27,7 @@ import { displayInputType, getLogsQueryByInputType } from './input_type_utils'; const StyledEuiAccordion = styled(EuiAccordion)` .ingest-integration-title-button { - padding: ${(props) => props.theme.eui.paddingSizes.m} - ${(props) => props.theme.eui.paddingSizes.m}; + padding: ${(props) => props.theme.eui.paddingSizes.m}; } &.euiAccordion-isOpen .ingest-integration-title-button { @@ -38,6 +37,10 @@ const StyledEuiAccordion = styled(EuiAccordion)` .euiTableRow:last-child .euiTableRowCell { border-bottom: none; } + + .euiIEFlexWrapFix { + min-width: 0; + } `; const CollapsablePanel: React.FC<{ id: string; title: React.ReactNode }> = ({ @@ -46,11 +49,11 @@ const CollapsablePanel: React.FC<{ id: string; title: React.ReactNode }> = ({ children, }) => { return ( - + {children} @@ -128,8 +131,9 @@ export const AgentDetailsIntegration: React.FunctionComponent<{ )}
    - + {title} - {description} + + {description} + ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx index 423467702e05af..fafe389d07b82c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx @@ -185,8 +185,6 @@ export const AgentLogsUI: React.FunctionComponent = memo(({ agen [http.basePath, state.start, state.end, logStreamQuery] ); - const [logsPanelRef, { height: logPanelHeight }] = useMeasure(); - const agentVersion = agent.local_metadata?.elastic?.agent?.version; const isLogFeatureAvailable = useMemo(() => { if (!agentVersion) { @@ -199,6 +197,13 @@ export const AgentLogsUI: React.FunctionComponent = memo(({ agen return semverGte(agentVersionWithPrerelease, '7.11.0'); }, [agentVersion]); + // Set absolute height on logs component (needed to render correctly in Safari) + // based on available height, or 600px, whichever is greater + const [logsPanelRef, { height: measuredlogPanelHeight }] = useMeasure(); + const logPanelHeight = useMemo(() => Math.max(measuredlogPanelHeight, 600), [ + measuredlogPanelHeight, + ]); + if (!isLogFeatureAvailable) { return ( Date: Fri, 19 Feb 2021 10:28:50 -0700 Subject: [PATCH 82/93] Unskip Search Sessions Management UI test (#90110) * Unskip Search Sessions Management UI test * more logging * logging * ci test * skip reload test * add tm task to archives used by dependent tests * --wip-- [skip ci] * revert jest affecting changes * fix search sessions archive * add pagination test * test organize * log cleanup * fix async in tests * remove obsolete test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/search/session/index.ts | 2 + .../sessions_mgmt/components/table/table.tsx | 9 +- .../dashboard/async_search/data.json | 31 ++ .../dashboard/async_search/mappings.json | 90 ++++ .../data/search_sessions/data.json.gz | Bin 1976 -> 2650 bytes .../data/search_sessions/mappings.json | 422 +++++++++--------- .../search_sessions_management_page.ts | 7 +- .../config.ts | 2 +- .../services/search_sessions.ts | 4 +- .../apps/dashboard/async_search/index.ts | 2 +- .../tests/apps/discover/index.ts | 2 +- .../search_sessions/sessions_management.ts | 149 +++++-- .../sessions_management_permissions.ts | 10 +- 13 files changed, 456 insertions(+), 274 deletions(-) diff --git a/x-pack/plugins/data_enhanced/common/search/session/index.ts b/x-pack/plugins/data_enhanced/common/search/session/index.ts index e83137308be98c..45b5c16bca9579 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/index.ts @@ -7,3 +7,5 @@ export * from './status'; export * from './types'; + +export const SEARCH_SESSIONS_TABLE_ID = 'searchSessionsMgmtUiTable'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index 6139f3ef8a847d..40ed0205d8dc93 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -9,11 +9,12 @@ import { EuiButton, EuiInMemoryTable, EuiSearchBarProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { CoreStart } from 'kibana/public'; import moment from 'moment'; -import React, { useCallback, useMemo, useRef, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import useInterval from 'react-use/lib/useInterval'; import { TableText } from '../'; import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '../..'; +import { SEARCH_SESSIONS_TABLE_ID } from '../../../../../common/search'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { getColumns } from '../../lib/get_columns'; import { UISession } from '../../types'; @@ -21,8 +22,6 @@ import { OnActionComplete } from '../actions'; import { getAppFilter } from './app_filter'; import { getStatusFilter } from './status_filter'; -const TABLE_ID = 'searchSessionsMgmtTable'; - interface Props { core: CoreStart; api: SearchSessionsMgmtAPI; @@ -107,8 +106,8 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, plugins, return ( {...props} - id={TABLE_ID} - data-test-subj={TABLE_ID} + id={SEARCH_SESSIONS_TABLE_ID} + data-test-subj={SEARCH_SESSIONS_TABLE_ID} rowProps={() => ({ 'data-test-subj': 'searchSessionsRow', })} diff --git a/x-pack/test/functional/es_archives/dashboard/async_search/data.json b/x-pack/test/functional/es_archives/dashboard/async_search/data.json index 486c73f711a6bf..90c890d0f041d3 100644 --- a/x-pack/test/functional/es_archives/dashboard/async_search/data.json +++ b/x-pack/test/functional/es_archives/dashboard/async_search/data.json @@ -243,3 +243,34 @@ } } +{ + "type": "doc", + "value": { + "id": "task:data_enhanced_search_sessions_monitor", + "index": ".kibana_task_manager_1", + "source": { + "references": [], + "task": { + "attempts": 0, + "ownerId": null, + "params": "{}", + "retryAt": "2020-11-30T15:43:39.626Z", + "runAt": "2020-11-30T15:43:08.277Z", + "scheduledAt": "2020-11-30T15:43:08.277Z", + "retryAt": null, + "schedule": { + "interval": "3s" + }, + "scope": [ + "testing" + ], + "startedAt": null, + "state": "{}", + "status": "idle", + "taskType": "search_sessions_monitor" + }, + "type": "task", + "updated_at": "2020-11-30T15:43:08.277Z" + } + } +} diff --git a/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json b/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json index 210fade40c648f..ee860fe973f602 100644 --- a/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json +++ b/x-pack/test/functional/es_archives/dashboard/async_search/mappings.json @@ -242,3 +242,93 @@ } } } + +{ + "type": "index", + "value": { + "aliases": { + ".kibana_task_manager": { + } + }, + "index": ".kibana_task_manager_1", + "mappings": { + "dynamic": "strict", + "properties": { + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "task": { + "properties": { + "attempts": { + "type": "integer" + }, + "ownerId": { + "type": "keyword" + }, + "params": { + "type": "text" + }, + "retryAt": { + "type": "date" + }, + "runAt": { + "type": "date" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledAt": { + "type": "date" + }, + "scope": { + "type": "keyword" + }, + "startedAt": { + "type": "date" + }, + "state": { + "type": "text" + }, + "status": { + "type": "keyword" + }, + "taskType": { + "type": "keyword" + }, + "user": { + "type": "keyword" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/data/search_sessions/data.json.gz b/x-pack/test/functional/es_archives/data/search_sessions/data.json.gz index 51e8c09f19247f979a9f1823ab5786b99cf849e4..fff020036a8e3de67513dbdb85f1bf96c850605d 100644 GIT binary patch literal 2650 zcmV-g3Z?ZQiwFoylPzEZ17u-zVJ>QOZ*BnXoNZGY*%HU!-%nxrdP$w<2j6LgnCyZP z7*w{ltn)O23nf#KSQXBknbY0V=l|;-B!BMg=C|XpySKCJ zM{f6*o!x7bOpEt_?(Ft`F+K?V3dgp!%9 zy1Ucb0d5~1G|s5Aby`QAvro}+yBpc}h}Z$|w`qemjy`?%cOzWCs*`!-a+6Qzj=0fq zoP<0qMl%S*YZm0e?<&Jj)S8bFPWc=JIz6UWNQcSqhYt#xW$<{?G2kK zxvPID6aDCBUGs&L7Afb2_KqQ8kd=ZaSdF|JD)Sk4Q3WpLK9lbEMwx)=!3IqCdcEn$Hy6?O>@Elvrm1$6U%lU|Yu|7>OM= z&RQG<4_p=7f@p1H&kd$1#D*;Aend`C&$QCyOVn;AHcifdYd5N4*gI_DY6kn(K&c=lI76^qwM2}%K)fN+QyT=_pXE$R&VAavJ|B#m z(OtLKJ?%utA3inARp;)vsD!OogW03D8psO7W4P%w1PO*xjj_|5%9t8$6>`LKcnQZ$ z48wtO9vp_;aE@WKWb4(Zj>n3g&_+E*o5eBiB4?IiD>Hm&G>`3op)UMyQ$R`t3b={$p*~wWM0jb_T zmMK~p%$SHB?7CRSl3R*F-V|n_bj-!td#;4$9yo)YBphJ`GhI+3hRqGFzJ@E^{UF&47GM4c&#MdcPn`83po+kW2j&#%W>$yvMIty!9g2zNi94`*4Y)}-gdZuhj5`TKWkns0>@Mj)t6VjQ@& zkS)e(fCZ?WCm{$oN56oCp~xy43n zZ<6|9r`1YptaH?uozR0{>!+7DCh3h$r;L2Nb<3>aU=vb-FhMcXP&+xP7=~LxC?XQT zqZP=wi@lDC23-d}Qq5oohXO-td7lmjH|Nx!*~3c~(cYmu9OT{VxY;=Boz`om6rDV_ zPh(4rC6Ka27~@dKpzzR8Ut!4@qlm`ZVz>*8!nWYf8uXo_r?p!N`diaD!AZ z4ys}VbR>a;76k&O7N55uaX-mJ_N~7dLn{!n>(DaS!PmYWi%l{KaQMqKWVeg;5|ds| z&E>o;8q9|u9N%5UH17{X6XxBU7-ys5!#)GEHfDQ7?-BA2itc%zk&l*U@HU})3_20= zPBYeBTy{DMN8s%PyRQ4;ratm>V~T$-=A~vhANf7J9}R~A+NwWxn&CR6)39^+HC)A_ zWAL&+Su{&|HZ86Q%~LXL++V;$*)~n`i`3-(>v_7*54Z2T!iU3?e3XR^zb(*K!u5I| zQ@9MYvWpYP2EzKc|6f~NG(Tg}cD5E>=fab+J?;zVz~rCU{Bw!RM^o4Pp`pfdkK;W| z-$|wB)=#tKLr4S2;V4@yd$`{^Iyl~M9$xH!{(KQNf4lflZB(24?c?U|!jKYy;0 zVMhz!I#rCjI6@(5HvMT8AOG)10JPDL)dntUr#1_8>Zw6TpBZ$M;HFuzomOy8zYWff zL+T4tx0HK3l;c~LdLNoQtX)-cbS=SZw27+8~J1| zWZ5X&;_VILO>FV@;&>}3cbks4l3x6%H9vtH-mK5$@Kb&!$BQ`eXNFr6(p8_xVZQyV zlP%s}DcQOZ*BnXn_E-cI26a<`zgHMx1Gi#`Ib0O%eKI> zvjs{*J8kJ?6k7%_j-A>vTZZX(UrElzNd*Z_vDeIjv|w46qw|-3(s1V7X*#=|QoXO~ zLF(%-HT{Ofg?PTx^e7PJ`IPvyAJC8#F%vbSB%oi#8{;|}lZ1@0D9zI%^Jfhw(Pc(h zlqT!eCG&rDy6BX+RhaMudrC6Pc)k%?`GQrq##YzG)k{+x^HJi|-dN?lwEvPm?gZ z?Dq`By|R^m%NFJD<06WK<6^>}JcPQ~^GwI?+Q{>;bd8+L(Y#B?x3TG3sAEyr>R?RA z9YSrlL$T!&8U~0rEhnzla51vpHM|EzoX>eye}mun$HIo=T95LZ^iyX_7^7Lz-)d%C zd3UAXvM7vb9PBZci5cp5-$WBxCZ9ij7SAl2&;wC+JX=Sk8mCDu+%M2Gqzl{bKng5($@y1_rywD z$d6YKw?&Ny7qi_ry9Rc9J7R-nB*}>{N=V#yU5_FX{Df7$VWc&-On4ucbV3uRCX8d4 z2$|OMO9dGG5MfHv+Q{%s`$q_~YW-%W%aNR?5C_*SPC#!)EKTEFtqn39$Ho=-cU9uJ z?bfE;l%1UBV$b%!d_j0SSFbi)_iU8=Dd&J_#N8=ut{RN++QG=H(XZ2}yeBWKncr8N z3b^EKaaq@1@`=3R14qg1s@mhXQ3v~`*O^^26u*X*!vV7mRL(6h; z=(wTlQ|g5Q4!Z;iTI&b#Ux)7w-{KE(K+eyncs#lK!#~6y-d$b~YQ7&H z3{D1kNDmns;N$)G*VF0wFh$3&&;Kp;j*^H)BpxtvOWSa?l*C>x3@O5Cc}N{S>U@k6 z-sVOwc%2k16=m|+C{36j8S)5~#g+H^4E5qd-@%q0Q2qPkWkS!mp)zHVWQQx-u$TfPFEf)u$ z=-TzG``z`c$myRX7sq@^;+)E|`E}wb>6~WiC0~ZQHk*JZ7d|gxX+}ZC|Fequ4;jlP zE%NeCIGWDHm#EF+Lz+;2qfN82%hyMPS10fG&qjNvr=#<;zek7r$NOh{!;`a(XkUZn z<|~kG8Q8OTB)M4w$rXrJk=)u$a$zfw92oFZNd79FOk>Iy7jV3F8t%w(ewwc>5IG5#c3#f@ebTE&q146TuZ|RuS9*g2Vo=^BPY0 zK8#~vcndY$^%_2x{+UqRse$4OJgX?~0>yy73XEX{!6t|5;Ap|w65bxChaRr@K z5cdG$0DuOAnt<@OCAG4r^%%zEYMbCs&Ig`W%)o%b#ZvqkSMA;6;y}E7Kx&8 zc);#|(wM3R3~z~w5K~1%$UnL5QhV2wp>?s0z(0fYG4Y68RC zq9Ft|s}whDNEB7!Sw(S36a@k_7t{oTw?#h)YA)zqL#Cz*%PM+9rY7wErh}ReFuWxy zLQr#!=A1DXwL0>|5;Ap|w65U(LqQ$=SL#354?0MKMm6A<1O1tF+e zA$Sd$nkqD_2o9N=u>YG4Y68RCq9Ft|S14XXrltzdDvCp;xpe7K!E&4snpyq#x KdKBy*oB#mBztLO( diff --git a/x-pack/test/functional/es_archives/data/search_sessions/mappings.json b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json index a3a56871269dff..61305d640fe3e8 100644 --- a/x-pack/test/functional/es_archives/data/search_sessions/mappings.json +++ b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json @@ -1,96 +1,8 @@ { "type": "index", "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", + "index": ".kibana", "mappings": { - "_meta": { - "migrationMappingPropertyHashes": { - "action": "6e96ac5e648f57523879661ea72525b7", - "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", - "alert": "49eb3350984bd2a162914d3776e70cfb", - "api_key_pending_invalidation": "16f515278a295f6245149ad7c5ddedb7", - "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", - "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", - "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", - "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", - "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", - "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", - "background-session": "dfd06597e582fdbbbc09f1a3615e6ce0", - "canvas-element": "7390014e1091044523666d97247392fc", - "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", - "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", - "cases": "477f214ff61acc3af26a7b7818e380c1", - "cases-comments": "8a50736330e953bca91747723a319593", - "cases-configure": "387c5f3a3bda7e0ae0dd4e106f914a69", - "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", - "config": "c63748b75f39d0c54de12d12c1ccbc20", - "core-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", - "dashboard": "40554caf09725935e2c02e02563a2d07", - "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", - "endpoint:user-artifact-manifest": "a0d7b04ad405eed54d76e279c3727862", - "enterprise_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", - "epm-packages": "0cbbb16506734d341a96aaed65ec6413", - "epm-packages-assets": "44621b2f6052ef966da47b7c3a00f33b", - "exception-list": "67f055ab8c10abd7b2ebfd969b836788", - "exception-list-agnostic": "67f055ab8c10abd7b2ebfd969b836788", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "fleet-agent-actions": "9511b565b1cc6441a42033db3d5de8e9", - "fleet-agent-events": "e20a508b6e805189356be381dbfac8db", - "fleet-agents": "cb661e8ede2b640c42c8e5ef99db0683", - "fleet-enrollment-api-keys": "a69ef7ae661dab31561d6c6f052ef2a7", - "graph-workspace": "27a94b2edcb0610c6aea54a7c56d7752", - "index-pattern": "45915a1ad866812242df474eb0479052", - "infrastructure-ui-source": "3d1b76c39bfb2cc8296b024d73854724", - "ingest-agent-policies": "8b0733cce189659593659dad8db426f0", - "ingest-outputs": "8854f34453a47e26f86a29f8f3b80b4e", - "ingest-package-policies": "c91ca97b1ff700f0fc64dc6b13d65a85", - "ingest_manager_settings": "02a03095f0e05b7a538fa801b88a217f", - "inventory-view": "3d1b76c39bfb2cc8296b024d73854724", - "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "lens": "52346cfec69ff7b47d5f0c12361a2797", - "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", - "map": "4a05b35c3a3a58fbc72dd0202dc3487f", - "maps-telemetry": "5ef305b18111b77789afefbd36b66171", - "metrics-explorer-view": "3d1b76c39bfb2cc8296b024d73854724", - "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "ml-job": "3bb64c31915acf93fc724af137a0891b", - "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "monitoring-telemetry": "2669d5ec15e82391cf58df4294ee9c68", - "namespace": "2f4316de49999235636386fe51dc06c1", - "namespaces": "2f4316de49999235636386fe51dc06c1", - "originId": "2f4316de49999235636386fe51dc06c1", - "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", - "references": "7997cf5a56cc02bdc9c93361bde732b0", - "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", - "search": "43012c7ebc4cb57054e0a490e4b43023", - "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", - "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", - "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", - "siem-ui-timeline": "d12c5474364d737d17252acf1dc4585c", - "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", - "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", - "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", - "spaces-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", - "tag": "83d55da58f6530f7055415717ec06474", - "telemetry": "36a616f7026dfa617d6655df850fe16d", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", - "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", - "type": "2f4316de49999235636386fe51dc06c1", - "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", - "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "updated_at": "00da57df13e94e9d98437d13ace4bfe0", - "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "uptime-dynamic-settings": "3d1b76c39bfb2cc8296b024d73854724", - "url": "c7f66a0df8b1b52f17c28c4adb111105", - "visualization": "f819cf6636b75c9e76ba733a0c6ef355", - "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" - } - }, "dynamic": "strict", "properties": { "action": { @@ -302,49 +214,6 @@ "dynamic": "false", "type": "object" }, - "search-session": { - "properties": { - "appId": { - "type": "keyword" - }, - "created": { - "type": "date" - }, - "touched": { - "type": "date" - }, - "expires": { - "type": "date" - }, - "idMapping": { - "enabled": false, - "type": "object" - }, - "initialState": { - "enabled": false, - "type": "object" - }, - "name": { - "type": "keyword" - }, - "persisted": { - "type": "boolean" - }, - "restoreState": { - "enabled": false, - "type": "object" - }, - "sessionId": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "urlGeneratorId": { - "type": "keyword" - } - } - }, "canvas-element": { "dynamic": "false", "properties": { @@ -519,6 +388,13 @@ } } }, + "settings": { + "properties": { + "syncAlerts": { + "type": "boolean" + } + } + }, "status": { "type": "keyword" }, @@ -528,6 +404,9 @@ "title": { "type": "keyword" }, + "type": { + "type": "keyword" + }, "updated_at": { "type": "date" }, @@ -551,6 +430,9 @@ "alertId": { "type": "keyword" }, + "associationType": { + "type": "keyword" + }, "comment": { "type": "text" }, @@ -672,6 +554,78 @@ } } }, + "cases-connector-mappings": { + "properties": { + "mappings": { + "properties": { + "action_type": { + "type": "keyword" + }, + "source": { + "type": "keyword" + }, + "target": { + "type": "keyword" + } + } + } + } + }, + "cases-sub-case": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, "cases-user-actions": { "properties": { "action": { @@ -828,6 +782,19 @@ }, "endpoint:user-artifact-manifest": { "properties": { + "artifacts": { + "properties": { + "artifactId": { + "index": false, + "type": "keyword" + }, + "policyId": { + "index": false, + "type": "keyword" + } + }, + "type": "nested" + }, "created": { "index": false, "type": "date" @@ -838,19 +805,6 @@ "semanticVersion": { "index": false, "type": "keyword" - }, - "artifacts": { - "type": "nested", - "properties": { - "policyId": { - "type": "keyword", - "index": false - }, - "artifactId": { - "type": "keyword", - "index": false - } - } } } }, @@ -1053,12 +1007,22 @@ "type": "keyword" }, "name": { + "fields": { + "text": { + "type": "text" + } + }, "type": "keyword" }, "os_types": { "type": "keyword" }, "tags": { + "fields": { + "text": { + "type": "text" + } + }, "type": "keyword" }, "tie_breaker_id": { @@ -1179,12 +1143,22 @@ "type": "keyword" }, "name": { + "fields": { + "text": { + "type": "text" + } + }, "type": "keyword" }, "os_types": { "type": "keyword" }, "tags": { + "fields": { + "text": { + "type": "text" + } + }, "type": "keyword" }, "tie_breaker_id": { @@ -1201,10 +1175,14 @@ } } }, - "file-upload-telemetry": { + "file-upload-usage-collection-telemetry": { "properties": { - "filesUploadedTotalCount": { - "type": "long" + "file_upload": { + "properties": { + "index_creation_count": { + "type": "long" + } + } } } }, @@ -1312,9 +1290,6 @@ "policy_revision": { "type": "integer" }, - "shared_id": { - "type": "keyword" - }, "type": { "type": "keyword" }, @@ -1428,6 +1403,12 @@ "is_default": { "type": "boolean" }, + "is_default_fleet_server": { + "type": "boolean" + }, + "is_managed": { + "type": "boolean" + }, "monitoring_enabled": { "index": false, "type": "keyword" @@ -1622,6 +1603,10 @@ } } }, + "legacy-url-alias": { + "dynamic": "false", + "type": "object" + }, "lens": { "properties": { "description": { @@ -1661,6 +1646,10 @@ }, "map": { "properties": { + "bounds": { + "dynamic": "false", + "type": "object" + }, "description": { "type": "text" }, @@ -1689,47 +1678,6 @@ "dynamic": "false", "type": "object" }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "config": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "index-pattern": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "search": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "space": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, "ml-job": { "properties": { "datafeed_id": { @@ -1753,17 +1701,6 @@ } } }, - "ml-telemetry": { - "properties": { - "file_data_visualizer": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, "monitoring-telemetry": { "properties": { "reportedClusterUuids": { @@ -1843,6 +1780,15 @@ "description": { "type": "text" }, + "grid": { + "enabled": false, + "type": "object" + }, + "hideChart": { + "doc_values": false, + "index": false, + "type": "boolean" + }, "hits": { "doc_values": false, "index": false, @@ -1856,6 +1802,9 @@ } } }, + "pre712": { + "type": "boolean" + }, "sort": { "doc_values": false, "index": false, @@ -1869,6 +1818,58 @@ } } }, + "search-session": { + "properties": { + "appId": { + "type": "keyword" + }, + "created": { + "type": "date" + }, + "expires": { + "type": "date" + }, + "idMapping": { + "enabled": false, + "type": "object" + }, + "initialState": { + "enabled": false, + "type": "object" + }, + "name": { + "type": "keyword" + }, + "persisted": { + "type": "boolean" + }, + "realmName": { + "type": "keyword" + }, + "realmType": { + "type": "keyword" + }, + "restoreState": { + "enabled": false, + "type": "object" + }, + "sessionId": { + "type": "keyword" + }, + "status": { + "type": "keyword" + }, + "touched": { + "type": "date" + }, + "urlGeneratorId": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, "search-telemetry": { "dynamic": "false", "type": "object" @@ -2192,10 +2193,14 @@ "type": "keyword" }, "sort": { + "dynamic": "false", "properties": { "columnId": { "type": "keyword" }, + "columnType": { + "type": "keyword" + }, "sortDirection": { "type": "keyword" } @@ -2389,13 +2394,6 @@ } } }, - "tsvb-validation-telemetry": { - "properties": { - "failedRequests": { - "type": "long" - } - } - }, "type": { "type": "keyword" }, @@ -2604,7 +2602,9 @@ "index": { "auto_expand_replicas": "0-1", "number_of_replicas": "0", - "number_of_shards": "1" + "number_of_shards": "1", + "priority": "10", + "refresh_interval": "1s" } } } diff --git a/x-pack/test/functional/page_objects/search_sessions_management_page.ts b/x-pack/test/functional/page_objects/search_sessions_management_page.ts index df4e99dd595d90..402569971691d9 100644 --- a/x-pack/test/functional/page_objects/search_sessions_management_page.ts +++ b/x-pack/test/functional/page_objects/search_sessions_management_page.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SEARCH_SESSIONS_TABLE_ID } from '../../../plugins/data_enhanced/common/search'; import { FtrProviderContext } from '../ftr_provider_context'; export function SearchSessionsPageProvider({ getService, getPageObjects }: FtrProviderContext) { @@ -23,7 +24,7 @@ export function SearchSessionsPageProvider({ getService, getPageObjects }: FtrPr }, async getList() { - const table = await testSubjects.find('searchSessionsMgmtTable'); + const table = await testSubjects.find(SEARCH_SESSIONS_TABLE_ID); const allRows = await table.findAllByTestSubject('searchSessionsRow'); return Promise.all( @@ -45,9 +46,7 @@ export function SearchSessionsPageProvider({ getService, getPageObjects }: FtrPr reload: async () => { log.debug('management ui: reload the status'); await actionsCell.click(); - await find.clickByCssSelector( - '[data-test-subj="sessionManagementPopoverAction-reload"]' - ); + await testSubjects.click('sessionManagementPopoverAction-reload'); }, delete: async () => { log.debug('management ui: delete the session'); diff --git a/x-pack/test/send_search_to_background_integration/config.ts b/x-pack/test/send_search_to_background_integration/config.ts index cc09fe8b568e05..2763ebb63c3efb 100644 --- a/x-pack/test/send_search_to_background_integration/config.ts +++ b/x-pack/test/send_search_to_background_integration/config.ts @@ -24,8 +24,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [ resolve(__dirname, './tests/apps/dashboard/async_search'), resolve(__dirname, './tests/apps/discover'), - resolve(__dirname, './tests/apps/management/search_sessions'), resolve(__dirname, './tests/apps/lens'), + resolve(__dirname, './tests/apps/management/search_sessions'), ], kbnTestServer: { diff --git a/x-pack/test/send_search_to_background_integration/services/search_sessions.ts b/x-pack/test/send_search_to_background_integration/services/search_sessions.ts index bf79d35178a60d..0d03a28dfb9017 100644 --- a/x-pack/test/send_search_to_background_integration/services/search_sessions.ts +++ b/x-pack/test/send_search_to_background_integration/services/search_sessions.ts @@ -137,7 +137,9 @@ export function SearchSessionsProvider({ getService }: FtrProviderContext) { .expect(200); const { saved_objects: savedObjects } = body as SavedObjectsFindResponse; - log.debug(`Found created search sessions: ${savedObjects.map(({ id }) => id)}`); + if (savedObjects.length) { + log.debug(`Found created search sessions: ${savedObjects.map(({ id }) => id)}`); + } await Promise.all( savedObjects.map(async (so) => { log.debug(`Deleting search session: ${so.id}`); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/index.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/index.ts index 5a912117fe445d..82642a640ce479 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/index.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/index.ts @@ -13,7 +13,7 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid const PageObjects = getPageObjects(['common']); const searchSessions = getService('searchSessions'); - describe('async search', function () { + describe('Dashboard', function () { this.tags('ciGroup3'); before(async () => { diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/discover/index.ts b/x-pack/test/send_search_to_background_integration/tests/apps/discover/index.ts index 42f7560b82f4f8..f2bbdf9c9287bb 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/discover/index.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/discover/index.ts @@ -13,7 +13,7 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid const PageObjects = getPageObjects(['common']); const searchSessions = getService('searchSessions'); - describe('async search', function () { + describe('Discover', function () { this.tags('ciGroup3'); before(async () => { diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts index f925cfb78a8c6e..d81a7ee12f616d 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts @@ -22,13 +22,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const log = getService('log'); - // FLAKY: https://github.com/elastic/kibana/issues/89069 - describe.skip('Search sessions Management UI', () => { + describe('Search Sessions Management UI', () => { describe('New search sessions', () => { before(async () => { await PageObjects.common.navigateToApp('dashboard'); log.debug('wait for dashboard landing page'); - retry.tryForTime(10000, async () => { + await retry.tryForTime(10000, async () => { testSubjects.existOrFail('dashboardLandingPage'); }); await searchSessions.markTourDone(); @@ -51,6 +50,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.waitFor(`wait for first item to complete`, async function () { const s = await PageObjects.searchSessionsManagement.getList(); + if (!s[0]) { + log.warning(`Expected item is not in the table!`); + } else { + log.debug(`First item status: ${s[0].status}`); + } return s[0] && s[0].status === 'complete'; }); @@ -72,22 +76,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await searchSessions.expectState('restored'); }); - // NOTE: this test depends on the previous one passing - it('Reloads as new session from management', async () => { - await PageObjects.searchSessionsManagement.goTo(); - - const searchSessionList = await PageObjects.searchSessionsManagement.getList(); - - expect(searchSessionList.length).to.be(1); - await searchSessionList[0].reload(); - - // embeddable has loaded - await PageObjects.dashboard.waitForRenderComplete(); - - // new search session was completed - await searchSessions.expectState('completed'); - }); - it('Deletes a session from management', async () => { await PageObjects.searchSessionsManagement.goTo(); @@ -122,34 +110,105 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await esArchiver.load('data/search_sessions'); const searchSessionList = await PageObjects.searchSessionsManagement.getList(); - expect(searchSessionList.length).to.be(10); + expectSnapshot(searchSessionList.map((ss) => [ss.app, ss.name, ss.created, ss.expires])) + .toMatchInline(` + Array [ + Array [ + "graph", + "[eCommerce] Orders Test 6 ", + "16 Feb, 2021, 00:00:00", + "--", + ], + Array [ + "lens", + "[eCommerce] Orders Test 7", + "15 Feb, 2021, 00:00:00", + "24 Feb, 2021, 00:00:00", + ], + Array [ + "apm", + "[eCommerce] Orders Test 8", + "14 Feb, 2021, 00:00:00", + "24 Feb, 2021, 00:00:00", + ], + Array [ + "appSearch", + "[eCommerce] Orders Test 9", + "13 Feb, 2021, 00:00:00", + "24 Feb, 2021, 00:00:00", + ], + Array [ + "auditbeat", + "[eCommerce] Orders Test 10", + "12 Feb, 2021, 00:00:00", + "24 Feb, 2021, 00:00:00", + ], + Array [ + "code", + "[eCommerce] Orders Test 11", + "11 Feb, 2021, 00:00:00", + "24 Feb, 2021, 00:00:00", + ], + Array [ + "console", + "[eCommerce] Orders Test 12", + "10 Feb, 2021, 00:00:00", + "24 Feb, 2021, 00:00:00", + ], + Array [ + "security", + "[eCommerce] Orders Test 5 ", + "9 Feb, 2021, 00:00:00", + "24 Feb, 2021, 00:00:00", + ], + Array [ + "visualize", + "[eCommerce] Orders Test 4 ", + "8 Feb, 2021, 00:00:00", + "--", + ], + Array [ + "canvas", + "[eCommerce] Orders Test 3", + "7 Feb, 2021, 00:00:00", + "24 Feb, 2021, 00:00:00", + ], + ] + `); + + await esArchiver.unload('data/search_sessions'); + }); + + it('has working pagination controls', async () => { + await esArchiver.load('data/search_sessions'); - expect(searchSessionList.map((ss) => ss.created)).to.eql([ - '25 Dec, 2020, 00:00:00', - '24 Dec, 2020, 00:00:00', - '23 Dec, 2020, 00:00:00', - '22 Dec, 2020, 00:00:00', - '21 Dec, 2020, 00:00:00', - '20 Dec, 2020, 00:00:00', - '19 Dec, 2020, 00:00:00', - '18 Dec, 2020, 00:00:00', - '17 Dec, 2020, 00:00:00', - '16 Dec, 2020, 00:00:00', - ]); - - expect(searchSessionList.map((ss) => ss.expires)).to.eql([ - '--', - '--', - '--', - '23 Dec, 2020, 00:00:00', - '22 Dec, 2020, 00:00:00', - '--', - '--', - '--', - '18 Dec, 2020, 00:00:00', - '17 Dec, 2020, 00:00:00', - ]); + log.debug(`loading first page of sessions`); + const sessionListFirst = await PageObjects.searchSessionsManagement.getList(); + expect(sessionListFirst.length).to.be(10); + + await testSubjects.click('pagination-button-next'); + + const sessionListSecond = await PageObjects.searchSessionsManagement.getList(); + expect(sessionListSecond.length).to.be(2); + + expectSnapshot(sessionListSecond.map((ss) => [ss.app, ss.name, ss.created, ss.expires])) + .toMatchInline(` + Array [ + Array [ + "discover", + "[eCommerce] Orders Test 2", + "6 Feb, 2021, 00:00:00", + "24 Feb, 2021, 00:00:00", + ], + Array [ + "dashboard", + "[eCommerce] Revenue Dashboard", + "5 Feb, 2021, 00:00:00", + "24 Feb, 2021, 00:00:00", + ], + ] + `); await esArchiver.unload('data/search_sessions'); }); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management_permissions.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management_permissions.ts index 48f4156afbe82b..ad22fd2cbaf714 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management_permissions.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management_permissions.ts @@ -22,8 +22,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const appsMenu = getService('appsMenu'); const managementMenu = getService('managementMenu'); - describe('Search sessions Management UI permissions', () => { - describe('Sessions management is not available if non of apps enable search sessions', () => { + describe('Search Sessions Management UI permissions', () => { + describe('Sessions management is not available', () => { before(async () => { await security.role.create('data_analyst', { elasticsearch: {}, @@ -56,13 +56,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.security.forceLogout(); }); - it('Sessions management is not available if non of apps enable search sessions', async () => { + it('if no apps enable search sessions', async () => { const links = await appsMenu.readLinks(); expect(links.map((link) => link.text)).to.not.contain('Stack Management'); }); }); - describe('Sessions management is available if one of apps enables search sessions', () => { + describe('Sessions management is available', () => { before(async () => { await security.role.create('data_analyst', { elasticsearch: {}, @@ -95,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.security.forceLogout(); }); - it('Sessions management is available if one of apps enables search sessions', async () => { + it('if one app enables search sessions', async () => { const links = await appsMenu.readLinks(); expect(links.map((link) => link.text)).to.contain('Stack Management'); await PageObjects.common.navigateToApp('management'); From 85bc8b0b42d4a6d343d12b33aae71f5db42ae852 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Fri, 19 Feb 2021 12:34:01 -0500 Subject: [PATCH 83/93] [Time to Visualize] Stay in Edit Mode After Dashboard Quicksave (#91729) * Make quicksave function stay in edit mode --- .../public/application/dashboard_app.tsx | 1 + .../application/dashboard_state_manager.ts | 11 ++- .../public/application/lib/save_dashboard.ts | 6 +- .../application/listing/confirm_overlays.tsx | 47 +++++----- .../listing/dashboard_unsaved_listing.tsx | 18 ++-- .../application/top_nav/dashboard_top_nav.tsx | 27 +++--- .../application/top_nav/get_top_nav_config.ts | 54 ++++++----- .../dashboard/public/dashboard_strings.ts | 90 ++++++++++++------- .../public/top_nav_menu/top_nav_menu_data.tsx | 1 + .../public/top_nav_menu/top_nav_menu_item.tsx | 1 + .../apps/dashboard/dashboard_save.ts | 6 +- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 13 files changed, 156 insertions(+), 110 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index f659fa002e922b..8466cf009db9df 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -294,6 +294,7 @@ export function DashboardApp({ }} viewMode={viewMode} lastDashboardId={savedDashboardId} + clearUnsavedChanges={() => setUnsavedChanges(false)} timefilter={data.query.timefilter.timefilter} onQuerySubmit={(_payload, isUpdate) => { if (isUpdate === false) { diff --git a/src/plugins/dashboard/public/application/dashboard_state_manager.ts b/src/plugins/dashboard/public/application/dashboard_state_manager.ts index e4b2afa8a46ea3..7f3f347e6e3aec 100644 --- a/src/plugins/dashboard/public/application/dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/dashboard_state_manager.ts @@ -345,7 +345,7 @@ export class DashboardStateManager { /** * Resets the state back to the last saved version of the dashboard. */ - public resetState() { + public resetState(resetViewMode: boolean) { // In order to show the correct warning, we have to store the unsaved // title on the dashboard object. We should fix this at some point, but this is how all the other object // save panels work at the moment. @@ -366,9 +366,14 @@ export class DashboardStateManager { this.stateDefaults.query = this.lastSavedDashboardFilters.query; // Need to make a copy to ensure they are not overwritten. this.stateDefaults.filters = [...this.getLastSavedFilterBars()]; - this.isDirty = false; - this.stateContainer.set(this.stateDefaults); + + if (resetViewMode) { + this.stateContainer.set(this.stateDefaults); + } else { + const currentViewMode = this.stateContainer.get().viewMode; + this.stateContainer.set({ ...this.stateDefaults, viewMode: currentViewMode }); + } } /** diff --git a/src/plugins/dashboard/public/application/lib/save_dashboard.ts b/src/plugins/dashboard/public/application/lib/save_dashboard.ts index 80392f61946cd6..6913fcda4c8e2c 100644 --- a/src/plugins/dashboard/public/application/lib/save_dashboard.ts +++ b/src/plugins/dashboard/public/application/lib/save_dashboard.ts @@ -11,6 +11,8 @@ import { SavedObjectSaveOpts } from '../../services/saved_objects'; import { updateSavedDashboard } from './update_saved_dashboard'; import { DashboardStateManager } from '../dashboard_state_manager'; +export type SavedDashboardSaveOpts = SavedObjectSaveOpts & { stayInEditMode?: boolean }; + /** * Saves the dashboard. * @param toJson A custom toJson function. Used because the previous code used @@ -23,7 +25,7 @@ export function saveDashboard( toJson: (obj: any) => string, timeFilter: TimefilterContract, dashboardStateManager: DashboardStateManager, - saveOptions: SavedObjectSaveOpts + saveOptions: SavedDashboardSaveOpts ): Promise { const savedDashboard = dashboardStateManager.savedDashboard; const appState = dashboardStateManager.appState; @@ -36,7 +38,7 @@ export function saveDashboard( // reset state only when save() was successful // e.g. save() could be interrupted if title is duplicated and not confirmed dashboardStateManager.lastSavedDashboardFilters = dashboardStateManager.getFilterState(); - dashboardStateManager.resetState(); + dashboardStateManager.resetState(!saveOptions.stayInEditMode); } return id; diff --git a/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx b/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx index d302bb4216bc49..b1e9af32ccd196 100644 --- a/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx +++ b/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx @@ -18,21 +18,23 @@ import { } from '@elastic/eui'; import React from 'react'; import { OverlayStart } from '../../../../../core/public'; -import { createConfirmStrings, leaveConfirmStrings } from '../../dashboard_strings'; +import { + createConfirmStrings, + discardConfirmStrings, + leaveEditModeConfirmStrings, +} from '../../dashboard_strings'; import { toMountPoint } from '../../services/kibana_react'; -export const confirmDiscardUnsavedChanges = ( - overlays: OverlayStart, - discardCallback: () => void, - cancelButtonText = leaveConfirmStrings.getCancelButtonText() -) => +export type DiscardOrKeepSelection = 'cancel' | 'discard' | 'keep'; + +export const confirmDiscardUnsavedChanges = (overlays: OverlayStart, discardCallback: () => void) => overlays - .openConfirm(leaveConfirmStrings.getDiscardSubtitle(), { - confirmButtonText: leaveConfirmStrings.getConfirmButtonText(), - cancelButtonText, + .openConfirm(discardConfirmStrings.getDiscardSubtitle(), { + confirmButtonText: discardConfirmStrings.getDiscardConfirmButtonText(), + cancelButtonText: discardConfirmStrings.getDiscardCancelButtonText(), buttonColor: 'danger', defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON, - title: leaveConfirmStrings.getDiscardTitle(), + title: discardConfirmStrings.getDiscardTitle(), }) .then((isConfirmed) => { if (isConfirmed) { @@ -40,8 +42,6 @@ export const confirmDiscardUnsavedChanges = ( } }); -export type DiscardOrKeepSelection = 'cancel' | 'discard' | 'keep'; - export const confirmDiscardOrKeepUnsavedChanges = ( overlays: OverlayStart ): Promise => { @@ -50,11 +50,13 @@ export const confirmDiscardOrKeepUnsavedChanges = ( toMountPoint( <> - {leaveConfirmStrings.getLeaveEditModeTitle()} + + {leaveEditModeConfirmStrings.getLeaveEditModeTitle()} + - {leaveConfirmStrings.getLeaveEditModeSubtitle()} + {leaveEditModeConfirmStrings.getLeaveEditModeSubtitle()} @@ -62,33 +64,34 @@ export const confirmDiscardOrKeepUnsavedChanges = ( data-test-subj="dashboardDiscardConfirmCancel" onClick={() => session.close()} > - {leaveConfirmStrings.getCancelButtonText()} + {leaveEditModeConfirmStrings.getLeaveEditModeCancelButtonText()} { session.close(); - resolve('keep'); + resolve('discard'); }} > - {leaveConfirmStrings.getKeepChangesText()} + {leaveEditModeConfirmStrings.getLeaveEditModeDiscardButtonText()} { session.close(); - resolve('discard'); + resolve('keep'); }} > - {leaveConfirmStrings.getConfirmButtonText()} + {leaveEditModeConfirmStrings.getLeaveEditModeKeepChangesText()} ), { 'data-test-subj': 'dashboardDiscardConfirmModal', + maxWidth: 550, } ); }); diff --git a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx index db50cfb638d64c..66e8b2348490a1 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx @@ -17,11 +17,7 @@ import { } from '@elastic/eui'; import React, { useCallback, useEffect, useState } from 'react'; import { DashboardSavedObject } from '../..'; -import { - createConfirmStrings, - dashboardUnsavedListingStrings, - getNewDashboardTitle, -} from '../../dashboard_strings'; +import { dashboardUnsavedListingStrings, getNewDashboardTitle } from '../../dashboard_strings'; import { useKibana } from '../../services/kibana_react'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_panel_storage'; import { DashboardAppServices, DashboardRedirect } from '../types'; @@ -136,14 +132,10 @@ export const DashboardUnsavedListing = ({ const onDiscard = useCallback( (id?: string) => { - confirmDiscardUnsavedChanges( - overlays, - () => { - dashboardPanelStorage.clearPanels(id); - refreshUnsavedDashboards(); - }, - createConfirmStrings.getCancelButtonText() - ); + confirmDiscardUnsavedChanges(overlays, () => { + dashboardPanelStorage.clearPanels(id); + refreshUnsavedDashboards(); + }); }, [overlays, refreshUnsavedDashboards, dashboardPanelStorage] ); diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index 11fb7f0cb56ff4..d279a6c219c9df 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -19,12 +19,7 @@ import { openAddPanelFlyout, ViewMode, } from '../../services/embeddable'; -import { - getSavedObjectFinder, - SavedObjectSaveOpts, - SaveResult, - showSaveModal, -} from '../../services/saved_objects'; +import { getSavedObjectFinder, SaveResult, showSaveModal } from '../../services/saved_objects'; import { NavAction } from '../../types'; import { DashboardSavedObject } from '../..'; @@ -48,6 +43,7 @@ import { OverlayRef } from '../../../../../core/public'; import { getNewDashboardTitle, unsavedChangesBadge } from '../../dashboard_strings'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_panel_storage'; import { DashboardContainer } from '..'; +import { SavedDashboardSaveOpts } from '../lib/save_dashboard'; export interface DashboardTopNavState { chromeIsVisible: boolean; @@ -64,13 +60,15 @@ export interface DashboardTopNavProps { timefilter: TimefilterContract; indexPatterns: IndexPattern[]; redirectTo: DashboardRedirect; - unsavedChanges?: boolean; + unsavedChanges: boolean; + clearUnsavedChanges: () => void; lastDashboardId?: string; viewMode: ViewMode; } export function DashboardTopNav({ dashboardStateManager, + clearUnsavedChanges, dashboardContainer, lastDashboardId, unsavedChanges, @@ -98,6 +96,7 @@ export function DashboardTopNav({ } = useKibana().services; const [state, setState] = useState({ chromeIsVisible: false }); + const [isSaveInProgress, setIsSaveInProgress] = useState(false); useEffect(() => { const visibleSubscription = chrome.getIsVisible$().subscribe((chromeIsVisible) => { @@ -177,7 +176,7 @@ export function DashboardTopNav({ } function discardChanges() { - dashboardStateManager.resetState(); + dashboardStateManager.resetState(true); dashboardStateManager.clearUnsavedPanels(); // We need to do a hard reset of the timepicker. appState will not reload like @@ -222,7 +221,7 @@ export function DashboardTopNav({ * @resolved {String} - The id of the doc */ const save = useCallback( - async (saveOptions: SavedObjectSaveOpts) => { + async (saveOptions: SavedDashboardSaveOpts) => { return saveDashboard(angular.toJson, timefilter, dashboardStateManager, saveOptions) .then(function (id) { if (id) { @@ -239,7 +238,6 @@ export function DashboardTopNav({ redirectTo({ destination: 'dashboard', id, useReplace: !lastDashboardId }); } else { chrome.docTitle.change(dashboardStateManager.savedDashboard.lastSavedTitle); - dashboardStateManager.switchViewMode(ViewMode.VIEW); } } return { id }; @@ -355,7 +353,8 @@ export function DashboardTopNav({ } } - save({}).then((response: SaveResult) => { + setIsSaveInProgress(true); + save({ stayInEditMode: true }).then((response: SaveResult) => { // If the save wasn't successful, put the original values back. if (!(response as { id: string }).id) { dashboardStateManager.setTitle(currentTitle); @@ -364,10 +363,13 @@ export function DashboardTopNav({ if (savedObjectsTagging) { dashboardStateManager.setTags(currentTags); } + } else { + clearUnsavedChanges(); } + setIsSaveInProgress(false); return response; }); - }, [save, savedObjectsTagging, dashboardStateManager]); + }, [save, savedObjectsTagging, dashboardStateManager, clearUnsavedChanges]); const runClone = useCallback(() => { const currentTitle = dashboardStateManager.getTitle(); @@ -467,6 +469,7 @@ export function DashboardTopNav({ hideWriteControls: dashboardCapabilities.hideWriteControls, isNewDashboard: !savedDashboard.id, isDirty: dashboardStateManager.isDirty, + isSaveInProgress, }); const badges = unsavedChanges diff --git a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts index 26eea1b5f718de..801ab54eb9839c 100644 --- a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts +++ b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { ViewMode } from '../../services/embeddable'; import { TopNavIds } from './top_nav_ids'; import { NavAction } from '../../types'; +import { TopNavMenuData } from '../../../../navigation/public'; /** * @param actions - A mapping of TopNavIds to an action function that should run when the @@ -20,7 +21,12 @@ import { NavAction } from '../../types'; export function getTopNavConfig( dashboardMode: ViewMode, actions: { [key: string]: NavAction }, - options: { hideWriteControls: boolean; isNewDashboard: boolean; isDirty: boolean } + options: { + hideWriteControls: boolean; + isNewDashboard: boolean; + isDirty: boolean; + isSaveInProgress?: boolean; + } ) { switch (dashboardMode) { case ViewMode.VIEW: @@ -36,20 +42,17 @@ export function getTopNavConfig( getEditConfig(actions[TopNavIds.ENTER_EDIT_MODE]), ]; case ViewMode.EDIT: - return options.isNewDashboard - ? [ - getOptionsConfig(actions[TopNavIds.OPTIONS]), - getShareConfig(actions[TopNavIds.SHARE]), - getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE]), - getSaveConfig(actions[TopNavIds.SAVE], options.isNewDashboard), - ] - : [ - getOptionsConfig(actions[TopNavIds.OPTIONS]), - getShareConfig(actions[TopNavIds.SHARE]), - getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE]), - getSaveConfig(actions[TopNavIds.SAVE]), - getQuickSave(actions[TopNavIds.QUICK_SAVE]), - ]; + const disableButton = options.isSaveInProgress; + const navItems: TopNavMenuData[] = [ + getOptionsConfig(actions[TopNavIds.OPTIONS], disableButton), + getShareConfig(actions[TopNavIds.SHARE], disableButton), + getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE], disableButton), + getSaveConfig(actions[TopNavIds.SAVE], options.isNewDashboard, disableButton), + ]; + if (!options.isNewDashboard) { + navItems.push(getQuickSave(actions[TopNavIds.QUICK_SAVE], disableButton, options.isDirty)); + } + return navItems; default: return []; } @@ -106,9 +109,12 @@ function getEditConfig(action: NavAction) { /** * @returns {kbnTopNavConfig} */ -function getQuickSave(action: NavAction) { +function getQuickSave(action: NavAction, isLoading?: boolean, isDirty?: boolean) { return { + isLoading, + disableButton: !isDirty, id: 'quick-save', + iconType: 'save', emphasize: true, label: getSaveButtonLabel(), description: i18n.translate('dashboard.topNave.saveConfigDescription', { @@ -122,10 +128,12 @@ function getQuickSave(action: NavAction) { /** * @returns {kbnTopNavConfig} */ -function getSaveConfig(action: NavAction, isNewDashboard = false) { +function getSaveConfig(action: NavAction, isNewDashboard = false, disableButton?: boolean) { return { + disableButton, id: 'save', label: isNewDashboard ? getSaveButtonLabel() : getSaveAsButtonLabel(), + iconType: isNewDashboard ? 'save' : undefined, description: i18n.translate('dashboard.topNave.saveAsConfigDescription', { defaultMessage: 'Save as a new dashboard', }), @@ -138,11 +146,12 @@ function getSaveConfig(action: NavAction, isNewDashboard = false) { /** * @returns {kbnTopNavConfig} */ -function getViewConfig(action: NavAction) { +function getViewConfig(action: NavAction, disableButton?: boolean) { return { + disableButton, id: 'cancel', label: i18n.translate('dashboard.topNave.cancelButtonAriaLabel', { - defaultMessage: 'cancel', + defaultMessage: 'Return', }), description: i18n.translate('dashboard.topNave.viewConfigDescription', { defaultMessage: 'Switch to view-only mode', @@ -172,7 +181,7 @@ function getCloneConfig(action: NavAction) { /** * @returns {kbnTopNavConfig} */ -function getShareConfig(action: NavAction | undefined) { +function getShareConfig(action: NavAction | undefined, disableButton?: boolean) { return { id: 'share', label: i18n.translate('dashboard.topNave.shareButtonAriaLabel', { @@ -184,15 +193,16 @@ function getShareConfig(action: NavAction | undefined) { testId: 'shareTopNavButton', run: action ?? (() => {}), // disable the Share button if no action specified - disableButton: !action, + disableButton: !action || disableButton, }; } /** * @returns {kbnTopNavConfig} */ -function getOptionsConfig(action: NavAction) { +function getOptionsConfig(action: NavAction, disableButton?: boolean) { return { + disableButton, id: 'options', label: i18n.translate('dashboard.topNave.optionsButtonAriaLabel', { defaultMessage: 'options', diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts index dad347b176c7ef..79a59d0cfa6051 100644 --- a/src/plugins/dashboard/public/dashboard_strings.ts +++ b/src/plugins/dashboard/public/dashboard_strings.ts @@ -199,6 +199,25 @@ export const getNewDashboardTitle = () => defaultMessage: 'New Dashboard', }); +export const getDashboard60Warning = () => + i18n.translate('dashboard.urlWasRemovedInSixZeroWarningMessage', { + defaultMessage: 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.', + }); + +export const dashboardReadonlyBadge = { + getText: () => + i18n.translate('dashboard.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + getTooltip: () => + i18n.translate('dashboard.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save dashboards', + }), +}; + +/* + Modals +*/ export const shareModalStrings = { getTopMenuCheckbox: () => i18n.translate('dashboard.embedUrlParamExtension.topMenu', { @@ -222,22 +241,6 @@ export const shareModalStrings = { }), }; -export const getDashboard60Warning = () => - i18n.translate('dashboard.urlWasRemovedInSixZeroWarningMessage', { - defaultMessage: 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.', - }); - -export const dashboardReadonlyBadge = { - getText: () => - i18n.translate('dashboard.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - getTooltip: () => - i18n.translate('dashboard.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save dashboards', - }), -}; - export const leaveConfirmStrings = { getLeaveTitle: () => i18n.translate('dashboard.appLeaveConfirmModal.unsavedChangesTitle', { @@ -247,33 +250,51 @@ export const leaveConfirmStrings = { i18n.translate('dashboard.appLeaveConfirmModal.unsavedChangesSubtitle', { defaultMessage: 'Leave Dashboard with unsaved work?', }), - getKeepChangesText: () => - i18n.translate('dashboard.appLeaveConfirmModal.keepUnsavedChangesButtonLabel', { - defaultMessage: 'Keep unsaved changes', + getLeaveCancelButtonText: () => + i18n.translate('dashboard.appLeaveConfirmModal.cancelButtonLabel', { + defaultMessage: 'Cancel', }), +}; + +export const leaveEditModeConfirmStrings = { getLeaveEditModeTitle: () => - i18n.translate('dashboard.changeViewModeConfirmModal.leaveEditMode', { - defaultMessage: 'Leave edit mode with unsaved work?', + i18n.translate('dashboard.changeViewModeConfirmModal.leaveEditModeTitle', { + defaultMessage: 'You have unsaved changes', }), getLeaveEditModeSubtitle: () => - i18n.translate('dashboard.changeViewModeConfirmModal.discardChangesOptionalDescription', { - defaultMessage: `If you discard your changes, there's no getting them back.`, + i18n.translate('dashboard.changeViewModeConfirmModal.description', { + defaultMessage: `You can keep or discard your changes on return to view mode. You can't recover discarded changes.`, + }), + getLeaveEditModeKeepChangesText: () => + i18n.translate('dashboard.changeViewModeConfirmModal.keepUnsavedChangesButtonLabel', { + defaultMessage: 'Keep changes', + }), + getLeaveEditModeDiscardButtonText: () => + i18n.translate('dashboard.changeViewModeConfirmModal.confirmButtonLabel', { + defaultMessage: 'Discard changes', + }), + getLeaveEditModeCancelButtonText: () => + i18n.translate('dashboard.changeViewModeConfirmModal.cancelButtonLabel', { + defaultMessage: 'Continue editing', }), +}; + +export const discardConfirmStrings = { getDiscardTitle: () => - i18n.translate('dashboard.changeViewModeConfirmModal.discardChangesTitle', { + i18n.translate('dashboard.discardChangesConfirmModal.discardChangesTitle', { defaultMessage: 'Discard changes to dashboard?', }), getDiscardSubtitle: () => - i18n.translate('dashboard.changeViewModeConfirmModal.discardChangesDescription', { + i18n.translate('dashboard.discardChangesConfirmModal.discardChangesDescription', { defaultMessage: `Once you discard your changes, there's no getting them back.`, }), - getConfirmButtonText: () => - i18n.translate('dashboard.changeViewModeConfirmModal.confirmButtonLabel', { + getDiscardConfirmButtonText: () => + i18n.translate('dashboard.discardChangesConfirmModal.confirmButtonLabel', { defaultMessage: 'Discard changes', }), - getCancelButtonText: () => - i18n.translate('dashboard.changeViewModeConfirmModal.cancelButtonLabel', { - defaultMessage: 'Continue editing', + getDiscardCancelButtonText: () => + i18n.translate('dashboard.discardChangesConfirmModal.cancelButtonLabel', { + defaultMessage: 'Cancel', }), }; @@ -290,13 +311,20 @@ export const createConfirmStrings = { i18n.translate('dashboard.createConfirmModal.confirmButtonLabel', { defaultMessage: 'Start over', }), - getContinueButtonText: () => leaveConfirmStrings.getCancelButtonText(), + getContinueButtonText: () => + i18n.translate('dashboard.createConfirmModal.continueButtonLabel', { + defaultMessage: 'Continue editing', + }), getCancelButtonText: () => i18n.translate('dashboard.createConfirmModal.cancelButtonLabel', { defaultMessage: 'Cancel', }), }; +/* + Error Messages +*/ + export const panelStorageErrorStrings = { getPanelsGetError: (message: string) => i18n.translate('dashboard.panelStorageError.getError', { diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx index 3a54c7ed011855..b6b056134361a7 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx @@ -20,6 +20,7 @@ export interface TopNavMenuData { disableButton?: boolean | (() => boolean); tooltip?: string | (() => string | undefined); emphasize?: boolean; + isLoading?: boolean; iconType?: string; iconSide?: EuiButtonProps['iconSide']; } diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx index ec91452badf365..523bf07f828c95 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx @@ -30,6 +30,7 @@ export function TopNavMenuItem(props: TopNavMenuData) { const commonButtonProps = { isDisabled: isDisabled(), onClick: handleClick, + isLoading: props.isLoading, iconType: props.iconType, iconSide: props.iconSide, 'data-test-subj': props.testId, diff --git a/test/functional/apps/dashboard/dashboard_save.ts b/test/functional/apps/dashboard/dashboard_save.ts index d1320b064b6d1f..0a0a2fc1dd2865 100644 --- a/test/functional/apps/dashboard/dashboard_save.ts +++ b/test/functional/apps/dashboard/dashboard_save.ts @@ -130,7 +130,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.clickQuickSave(); await testSubjects.existOrFail('saveDashboardSuccess'); - await testSubjects.existOrFail('dashboardEditMode'); + }); + + it('Stays in edit mode after performing a quick save', async function () { + await PageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.existOrFail('dashboardQuickSaveMenuItem'); }); }); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1162a9bf00c70e..16712da1d7b2ea 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -589,8 +589,6 @@ "dashboard.badge.readOnly.tooltip": "ダッシュボードを保存できません", "dashboard.changeViewModeConfirmModal.cancelButtonLabel": "編集を続行", "dashboard.changeViewModeConfirmModal.confirmButtonLabel": "変更を破棄", - "dashboard.changeViewModeConfirmModal.discardChangesDescription": "変更を破棄すると、元に戻すことはできません。", - "dashboard.changeViewModeConfirmModal.discardChangesTitle": "ダッシュボードへの変更を破棄しますか?", "dashboard.cloneModal.cloneDashboardTitleAriaLabel": "クローンダッシュボードタイトル", "dashboard.dashboardAppBreadcrumbsTitle": "ダッシュボード", "dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage": "ダッシュボードが読み込めません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index fc658ae8ce719e..e89fc62a21db68 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -589,8 +589,6 @@ "dashboard.badge.readOnly.tooltip": "无法保存仪表板", "dashboard.changeViewModeConfirmModal.cancelButtonLabel": "继续编辑", "dashboard.changeViewModeConfirmModal.confirmButtonLabel": "放弃更改", - "dashboard.changeViewModeConfirmModal.discardChangesDescription": "放弃更改后,它们将无法恢复。", - "dashboard.changeViewModeConfirmModal.discardChangesTitle": "放弃对仪表板的更改?", "dashboard.cloneModal.cloneDashboardTitleAriaLabel": "克隆仪表板标题", "dashboard.dashboardAppBreadcrumbsTitle": "仪表板", "dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage": "无法加载仪表板。", From 857300b1477ac3ab6cea1988dadc9997e2780d39 Mon Sep 17 00:00:00 2001 From: Brandon Morelli Date: Fri, 19 Feb 2021 09:51:10 -0800 Subject: [PATCH 84/93] docs: update dependencies table bug (#91964) --- docs/apm/service-overview.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/apm/service-overview.asciidoc b/docs/apm/service-overview.asciidoc index 5fd214e6ce613a..36d021d64456e5 100644 --- a/docs/apm/service-overview.asciidoc +++ b/docs/apm/service-overview.asciidoc @@ -62,8 +62,8 @@ each dependency. By default, dependencies are sorted by _Impact_ to show the mos If there is a particular dependency you are interested in, click *View service map* to view the related <>. -IMPORTANT: A known issue prevents Real User Monitoring (RUM) dependencies from being shown in the -*Dependencies* table. We are working on a fix for this issue. +NOTE: Displaying dependencies for services instrumented with the Real User Monitoring (RUM) agent +requires an agent version ≥ v5.6.3. [role="screenshot"] image::apm/images/spans-dependencies.png[Span type duration and dependencies] From 405255cd9704079ede5a5c7c2274e90731de6b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Fri, 19 Feb 2021 12:55:29 -0500 Subject: [PATCH 85/93] [APM] Break down error table api removing the sparklines (#89138) * breaking error table api * shows loading state while fetching metrics * adding api tests * removing pagination from server * adding API test * refactoring * fixing license * renaming apis * fixing some stuff * addressing PR comments * adding request id * addressing PR comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Dario Gieselaar --- .../service_overview.test.tsx | 3 +- .../get_column.tsx | 94 +++++++ .../service_overview_errors_table/index.tsx | 197 ++++++--------- ...rvice_error_group_comparison_statistics.ts | 105 ++++++++ ..._service_error_group_primary_statistics.ts | 96 ++++++++ .../apm/server/routes/create_apm_api.ts | 6 +- x-pack/plugins/apm/server/routes/services.ts | 70 ++++-- .../plugins/apm/server/routes/transactions.ts | 2 +- .../test/apm_api_integration/tests/index.ts | 3 +- .../tests/service_overview/error_groups.ts | 229 ------------------ .../error_groups_comparison_statistics.snap | 133 ++++++++++ .../error_groups_comparison_statistics.ts | 110 +++++++++ .../error_groups_primary_statistics.ts | 115 +++++++++ 13 files changed, 774 insertions(+), 389 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_column.tsx create mode 100644 x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_comparison_statistics.ts create mode 100644 x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_primary_statistics.ts delete mode 100644 x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts create mode 100644 x-pack/test/apm_api_integration/tests/services/__snapshots__/error_groups_comparison_statistics.snap create mode 100644 x-pack/test/apm_api_integration/tests/services/error_groups_comparison_statistics.ts create mode 100644 x-pack/test/apm_api_integration/tests/services/error_groups_primary_statistics.ts diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx index 999718e754c619..f6ffec46f9f513 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx @@ -82,9 +82,8 @@ describe('ServiceOverview', () => { /* eslint-disable @typescript-eslint/naming-convention */ const calls = { - 'GET /api/apm/services/{serviceName}/error_groups': { + 'GET /api/apm/services/{serviceName}/error_groups/primary_statistics': { error_groups: [], - total_error_groups: 0, }, 'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics': { transactionGroups: [], diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_column.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_column.tsx new file mode 100644 index 00000000000000..94913c1678d219 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/get_column.tsx @@ -0,0 +1,94 @@ +/* + * 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 { EuiBasicTableColumn } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { asInteger } from '../../../../../common/utils/formatters'; +import { px, unit } from '../../../../style/variables'; +import { SparkPlot } from '../../../shared/charts/spark_plot'; +import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; +import { TimestampTooltip } from '../../../shared/TimestampTooltip'; +import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; + +type ErrorGroupPrimaryStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/primary_statistics'>; +type ErrorGroupComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/comparison_statistics'>; + +export function getColumns({ + serviceName, + errorGroupComparisonStatistics, +}: { + serviceName: string; + errorGroupComparisonStatistics: ErrorGroupComparisonStatistics; +}): Array> { + return [ + { + field: 'name', + name: i18n.translate('xpack.apm.serviceOverview.errorsTableColumnName', { + defaultMessage: 'Name', + }), + render: (_, { name, group_id: errorGroupId }) => { + return ( + + {name} + + } + /> + ); + }, + }, + { + field: 'last_seen', + name: i18n.translate( + 'xpack.apm.serviceOverview.errorsTableColumnLastSeen', + { + defaultMessage: 'Last seen', + } + ), + render: (_, { last_seen: lastSeen }) => { + return ; + }, + width: px(unit * 9), + }, + { + field: 'occurrences', + name: i18n.translate( + 'xpack.apm.serviceOverview.errorsTableColumnOccurrences', + { + defaultMessage: 'Occurrences', + } + ), + width: px(unit * 12), + render: (_, { occurrences, group_id: errorGroupId }) => { + const timeseries = + errorGroupComparisonStatistics?.[errorGroupId]?.timeseries; + return ( + + ); + }, + }, + ]; +} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index f7f5db32e986cc..109bf0483f2b0e 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -7,40 +7,26 @@ import { EuiBasicTable, - EuiBasicTableColumn, EuiFlexGroup, EuiFlexItem, EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { orderBy } from 'lodash'; import React, { useState } from 'react'; -import { asInteger } from '../../../../../common/utils/formatters'; +import uuid from 'uuid'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { px, unit } from '../../../../style/variables'; -import { SparkPlot } from '../../../shared/charts/spark_plot'; -import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; import { ErrorOverviewLink } from '../../../shared/Links/apm/ErrorOverviewLink'; import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; -import { TimestampTooltip } from '../../../shared/TimestampTooltip'; -import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; +import { getColumns } from './get_column'; interface Props { serviceName: string; } -interface ErrorGroupItem { - name: string; - last_seen: number; - group_id: string; - occurrences: { - value: number; - timeseries: Array<{ x: number; y: number }> | null; - }; -} - type SortDirection = 'asc' | 'desc'; type SortField = 'name' | 'last_seen' | 'occurrences'; @@ -50,6 +36,11 @@ const DEFAULT_SORT = { field: 'occurrences' as const, }; +const INITIAL_STATE = { + items: [], + requestId: undefined, +}; + export function ServiceOverviewErrorsTable({ serviceName }: Props) { const { urlParams: { environment, start, end }, @@ -67,88 +58,16 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { sort: DEFAULT_SORT, }); - const columns: Array> = [ - { - field: 'name', - name: i18n.translate('xpack.apm.serviceOverview.errorsTableColumnName', { - defaultMessage: 'Name', - }), - render: (_, { name, group_id: errorGroupId }) => { - return ( - - {name} - - } - /> - ); - }, - }, - { - field: 'last_seen', - name: i18n.translate( - 'xpack.apm.serviceOverview.errorsTableColumnLastSeen', - { - defaultMessage: 'Last seen', - } - ), - render: (_, { last_seen: lastSeen }) => { - return ; - }, - width: px(unit * 9), - }, - { - field: 'occurrences', - name: i18n.translate( - 'xpack.apm.serviceOverview.errorsTableColumnOccurrences', - { - defaultMessage: 'Occurrences', - } - ), - width: px(unit * 12), - render: (_, { occurrences }) => { - return ( - - ); - }, - }, - ]; + const { pageIndex, sort } = tableOptions; - const { - data = { - totalItemCount: 0, - items: [], - tableOptions: { - pageIndex: 0, - sort: DEFAULT_SORT, - }, - }, - status, - } = useFetcher( + const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { if (!start || !end || !transactionType) { return; } - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/error_groups', + endpoint: + 'GET /api/apm/services/{serviceName}/error_groups/primary_statistics', params: { path: { serviceName }, query: { @@ -156,46 +75,68 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { start, end, uiFilters: JSON.stringify(uiFilters), - size: PAGE_SIZE, - numBuckets: 20, - pageIndex: tableOptions.pageIndex, - sortField: tableOptions.sort.field, - sortDirection: tableOptions.sort.direction, transactionType, }, }, }).then((response) => { return { + requestId: uuid(), items: response.error_groups, - totalItemCount: response.total_error_groups, - tableOptions: { - pageIndex: tableOptions.pageIndex, - sort: { - field: tableOptions.sort.field, - direction: tableOptions.sort.direction, - }, - }, }; }); }, - [ - environment, - start, - end, - serviceName, - uiFilters, - tableOptions.pageIndex, - tableOptions.sort.field, - tableOptions.sort.direction, - transactionType, - ] + [environment, start, end, serviceName, uiFilters, transactionType] ); - const { + const { requestId, items } = data; + const currentPageErrorGroups = orderBy( items, - totalItemCount, - tableOptions: { pageIndex, sort }, - } = data; + sort.field, + sort.direction + ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE); + + const groupIds = JSON.stringify( + currentPageErrorGroups.map(({ group_id: groupId }) => groupId).sort() + ); + const { + data: errorGroupComparisonStatistics, + status: errorGroupComparisonStatisticsStatus, + } = useFetcher( + (callApmApi) => { + if ( + requestId && + currentPageErrorGroups.length && + start && + end && + transactionType + ) { + return callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/error_groups/comparison_statistics', + params: { + path: { serviceName }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + numBuckets: 20, + transactionType, + groupIds, + }, + }, + }); + } + }, + // only fetches agg results when requestId or group ids change + // eslint-disable-next-line react-hooks/exhaustive-deps + [requestId, groupIds], + { preservePreviousData: false } + ); + + const columns = getColumns({ + serviceName, + errorGroupComparisonStatistics: errorGroupComparisonStatistics ?? {}, + }); return ( @@ -228,15 +169,18 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { > diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_comparison_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_comparison_statistics.ts new file mode 100644 index 00000000000000..3655fa513dfb42 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_comparison_statistics.ts @@ -0,0 +1,105 @@ +/* + * 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 { keyBy } from 'lodash'; +import { + ERROR_GROUP_ID, + SERVICE_NAME, + TRANSACTION_TYPE, +} from '../../../../common/elasticsearch_fieldnames'; +import { ProcessorEvent } from '../../../../common/processor_event'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; +import { withApmSpan } from '../../../utils/with_apm_span'; +import { getBucketSize } from '../../helpers/get_bucket_size'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; + +export async function getServiceErrorGroupComparisonStatistics({ + serviceName, + setup, + numBuckets, + transactionType, + groupIds, + environment, +}: { + serviceName: string; + setup: Setup & SetupTimeRange; + numBuckets: number; + transactionType: string; + groupIds: string[]; + environment?: string; +}) { + return withApmSpan( + 'get_service_error_group_comparison_statistics', + async () => { + const { apmEventClient, start, end, esFilter } = setup; + + const { intervalString } = getBucketSize({ start, end, numBuckets }); + + const timeseriesResponse = await apmEventClient.search({ + apm: { + events: [ProcessorEvent.error], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + { terms: { [ERROR_GROUP_ID]: groupIds } }, + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, + ], + }, + }, + aggs: { + error_groups: { + terms: { + field: ERROR_GROUP_ID, + size: 500, + }, + aggs: { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, + }, + }, + }, + }, + }, + }, + }); + + if (!timeseriesResponse.aggregations) { + return {}; + } + + const groups = timeseriesResponse.aggregations.error_groups.buckets.map( + (bucket) => { + const groupId = bucket.key as string; + return { + groupId, + timeseries: bucket.timeseries.buckets.map((timeseriesBucket) => { + return { + x: timeseriesBucket.key, + y: timeseriesBucket.doc_count, + }; + }), + }; + } + ); + + return keyBy(groups, 'groupId'); + } + ); +} diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_primary_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_primary_statistics.ts new file mode 100644 index 00000000000000..e6c1c5db8f2ca8 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_primary_statistics.ts @@ -0,0 +1,96 @@ +/* + * 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 { + ERROR_EXC_MESSAGE, + ERROR_GROUP_ID, + ERROR_LOG_MESSAGE, + SERVICE_NAME, + TRANSACTION_TYPE, +} from '../../../../common/elasticsearch_fieldnames'; +import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; +import { ProcessorEvent } from '../../../../common/processor_event'; +import { environmentQuery, rangeQuery } from '../../../../common/utils/queries'; +import { withApmSpan } from '../../../utils/with_apm_span'; +import { getErrorName } from '../../helpers/get_error_name'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; + +export function getServiceErrorGroupPrimaryStatistics({ + serviceName, + setup, + transactionType, + environment, +}: { + serviceName: string; + setup: Setup & SetupTimeRange; + transactionType: string; + environment?: string; +}) { + return withApmSpan('get_service_error_group_primary_statistics', async () => { + const { apmEventClient, start, end, esFilter } = setup; + + const response = await apmEventClient.search({ + apm: { + events: [ProcessorEvent.error], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...esFilter, + ], + }, + }, + aggs: { + error_groups: { + terms: { + field: ERROR_GROUP_ID, + size: 500, + order: { + _count: 'desc', + }, + }, + aggs: { + sample: { + top_hits: { + size: 1, + _source: [ERROR_LOG_MESSAGE, ERROR_EXC_MESSAGE, '@timestamp'], + sort: { + '@timestamp': 'desc', + }, + }, + }, + }, + }, + }, + }, + }); + + const errorGroups = + response.aggregations?.error_groups.buckets.map((bucket) => ({ + group_id: bucket.key as string, + name: + getErrorName(bucket.sample.hits.hits[0]._source) ?? + NOT_AVAILABLE_LABEL, + last_seen: new Date( + bucket.sample.hits.hits[0]?._source['@timestamp'] + ).getTime(), + occurrences: bucket.doc_count, + })) ?? []; + + return { + is_aggregation_accurate: + (response.aggregations?.error_groups.sum_other_doc_count ?? 0) === 0, + error_groups: errorGroups, + }; + }); +} diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index 822a45fca269fd..c96e02f6c18215 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -24,7 +24,8 @@ import { serviceNodeMetadataRoute, serviceAnnotationsRoute, serviceAnnotationsCreateRoute, - serviceErrorGroupsRoute, + serviceErrorGroupsPrimaryStatisticsRoute, + serviceErrorGroupsComparisonStatisticsRoute, serviceThroughputRoute, serviceDependenciesRoute, serviceMetadataDetailsRoute, @@ -126,12 +127,13 @@ const createApmApi = () => { .add(serviceNodeMetadataRoute) .add(serviceAnnotationsRoute) .add(serviceAnnotationsCreateRoute) - .add(serviceErrorGroupsRoute) + .add(serviceErrorGroupsPrimaryStatisticsRoute) .add(serviceThroughputRoute) .add(serviceDependenciesRoute) .add(serviceMetadataDetailsRoute) .add(serviceMetadataIconsRoute) .add(serviceInstancesRoute) + .add(serviceErrorGroupsComparisonStatisticsRoute) // Agent configuration .add(getSingleAgentConfigurationRoute) diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index 24c7c6e3e23d7e..2ce41f3d1e1a09 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -16,15 +16,17 @@ import { getServiceAnnotations } from '../lib/services/annotations'; import { getServices } from '../lib/services/get_services'; import { getServiceAgentName } from '../lib/services/get_service_agent_name'; import { getServiceDependencies } from '../lib/services/get_service_dependencies'; -import { getServiceErrorGroups } from '../lib/services/get_service_error_groups'; +import { getServiceErrorGroupPrimaryStatistics } from '../lib/services/get_service_error_groups/get_service_error_group_primary_statistics'; +import { getServiceErrorGroupComparisonStatistics } from '../lib/services/get_service_error_groups/get_service_error_group_comparison_statistics'; import { getServiceInstances } from '../lib/services/get_service_instances'; import { getServiceMetadataDetails } from '../lib/services/get_service_metadata_details'; import { getServiceMetadataIcons } from '../lib/services/get_service_metadata_icons'; import { getServiceNodeMetadata } from '../lib/services/get_service_node_metadata'; import { getServiceTransactionTypes } from '../lib/services/get_service_transaction_types'; import { getThroughput } from '../lib/services/get_throughput'; -import { offsetPreviousPeriodCoordinates } from '../utils/offset_previous_period_coordinate'; import { createRoute } from './create_route'; +import { offsetPreviousPeriodCoordinates } from '../utils/offset_previous_period_coordinate'; +import { jsonRt } from '../../common/runtime_types/json_rt'; import { comparisonRangeRt, environmentRt, @@ -276,8 +278,42 @@ export const serviceAnnotationsCreateRoute = createRoute({ }, }); -export const serviceErrorGroupsRoute = createRoute({ - endpoint: 'GET /api/apm/services/{serviceName}/error_groups', +export const serviceErrorGroupsPrimaryStatisticsRoute = createRoute({ + endpoint: + 'GET /api/apm/services/{serviceName}/error_groups/primary_statistics', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + environmentRt, + rangeRt, + uiFiltersRt, + t.type({ + transactionType: t.string, + }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + + const { + path: { serviceName }, + query: { transactionType, environment }, + } = context.params; + return getServiceErrorGroupPrimaryStatistics({ + serviceName, + setup, + transactionType, + environment, + }); + }, +}); + +export const serviceErrorGroupsComparisonStatisticsRoute = createRoute({ + endpoint: + 'GET /api/apm/services/{serviceName}/error_groups/comparison_statistics', params: t.type({ path: t.type({ serviceName: t.string, @@ -287,16 +323,9 @@ export const serviceErrorGroupsRoute = createRoute({ rangeRt, uiFiltersRt, t.type({ - size: toNumberRt, numBuckets: toNumberRt, - pageIndex: toNumberRt, - sortDirection: t.union([t.literal('asc'), t.literal('desc')]), - sortField: t.union([ - t.literal('last_seen'), - t.literal('occurrences'), - t.literal('name'), - ]), transactionType: t.string, + groupIds: jsonRt.pipe(t.array(t.string)), }), ]), }), @@ -306,27 +335,16 @@ export const serviceErrorGroupsRoute = createRoute({ const { path: { serviceName }, - query: { - environment, - numBuckets, - pageIndex, - size, - sortDirection, - sortField, - transactionType, - }, + query: { environment, numBuckets, transactionType, groupIds }, } = context.params; - return getServiceErrorGroups({ + return getServiceErrorGroupComparisonStatistics({ environment, serviceName, setup, - size, numBuckets, - pageIndex, - sortDirection, - sortField, transactionType, + groupIds, }); }, }); diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index 5a4be216a817c2..960cc7f5264242 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -117,7 +117,7 @@ export const transactionGroupsComparisonStatisticsRoute = createRoute({ rangeRt, uiFiltersRt, t.type({ - transactionNames: jsonRt, + transactionNames: jsonRt.pipe(t.array(t.string)), numBuckets: toNumberRt, transactionType: t.string, latencyAggregationType: latencyAggregationTypeRt, diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index 72ca22ae749ca7..3884b3ae750a5c 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -34,7 +34,6 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./service_maps/service_maps')); loadTestFile(require.resolve('./service_overview/dependencies')); - loadTestFile(require.resolve('./service_overview/error_groups')); loadTestFile(require.resolve('./service_overview/instances')); loadTestFile(require.resolve('./services/agent_name')); @@ -44,6 +43,8 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./services/throughput')); loadTestFile(require.resolve('./services/top_services')); loadTestFile(require.resolve('./services/transaction_types')); + loadTestFile(require.resolve('./services/error_groups_primary_statistics')); + loadTestFile(require.resolve('./services/error_groups_comparison_statistics')); loadTestFile(require.resolve('./settings/anomaly_detection/basic')); loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); diff --git a/x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts b/x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts deleted file mode 100644 index fb7376a77382f1..00000000000000 --- a/x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import qs from 'querystring'; -import { pick, uniqBy } from 'lodash'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import archives from '../../common/fixtures/es_archiver/archives_metadata'; -import { registry } from '../../common/registry'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - const archiveName = 'apm_8.0.0'; - const { start, end } = archives[archiveName]; - - registry.when( - 'Service overview error groups when data is not loaded', - { config: 'basic', archives: [] }, - () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ - start, - end, - uiFilters: '{}', - size: 5, - numBuckets: 20, - pageIndex: 0, - sortDirection: 'desc', - sortField: 'occurrences', - transactionType: 'request', - })}` - ); - - expect(response.status).to.be(200); - expect(response.body).to.eql({ - total_error_groups: 0, - error_groups: [], - is_aggregation_accurate: true, - }); - }); - } - ); - - registry.when( - 'Service overview error groups when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - it('returns the correct data', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ - start, - end, - uiFilters: '{}', - size: 5, - numBuckets: 20, - pageIndex: 0, - sortDirection: 'desc', - sortField: 'occurrences', - transactionType: 'request', - })}` - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body.total_error_groups).toMatchInline(`5`); - - expectSnapshot(response.body.error_groups.map((group: any) => group.name)).toMatchInline(` - Array [ - "Could not write JSON: Null return value from advice does not match primitive return type for: public abstract double co.elastic.apm.opbeans.repositories.Numbers.getRevenue(); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Null return value from advice does not match primitive return type for: public abstract double co.elastic.apm.opbeans.repositories.Numbers.getRevenue() (through reference chain: co.elastic.apm.opbeans.repositories.Stats[\\"numbers\\"]->com.sun.proxy.$Proxy132[\\"revenue\\"])", - "java.io.IOException: Connection reset by peer", - "java.io.IOException: Connection reset by peer", - "Could not write JSON: Unable to find co.elastic.apm.opbeans.model.Customer with id 7173; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unable to find co.elastic.apm.opbeans.model.Customer with id 7173 (through reference chain: co.elastic.apm.opbeans.model.Customer_$$_jvst101_3[\\"email\\"])", - "Request method 'POST' not supported", - ] - `); - - expectSnapshot(response.body.error_groups.map((group: any) => group.occurrences.value)) - .toMatchInline(` - Array [ - 5, - 3, - 2, - 1, - 1, - ] - `); - - const firstItem = response.body.error_groups[0]; - - expectSnapshot(pick(firstItem, 'group_id', 'last_seen', 'name', 'occurrences.value')) - .toMatchInline(` - Object { - "group_id": "051f95eabf120ebe2f8b0399fe3e54c5", - "last_seen": 1607437366098, - "name": "Could not write JSON: Null return value from advice does not match primitive return type for: public abstract double co.elastic.apm.opbeans.repositories.Numbers.getRevenue(); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Null return value from advice does not match primitive return type for: public abstract double co.elastic.apm.opbeans.repositories.Numbers.getRevenue() (through reference chain: co.elastic.apm.opbeans.repositories.Stats[\\"numbers\\"]->com.sun.proxy.$Proxy132[\\"revenue\\"])", - "occurrences": Object { - "value": 5, - }, - } - `); - - const visibleDataPoints = firstItem.occurrences.timeseries.filter(({ y }: any) => y > 0); - expectSnapshot(visibleDataPoints.length).toMatchInline(`4`); - }); - - it('sorts items in the correct order', async () => { - const descendingResponse = await supertest.get( - `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ - start, - end, - uiFilters: '{}', - size: 5, - numBuckets: 20, - pageIndex: 0, - sortDirection: 'desc', - sortField: 'occurrences', - transactionType: 'request', - })}` - ); - - expect(descendingResponse.status).to.be(200); - - const descendingOccurrences = descendingResponse.body.error_groups.map( - (item: any) => item.occurrences.value - ); - - expect(descendingOccurrences).to.eql(descendingOccurrences.concat().sort().reverse()); - - const ascendingResponse = await supertest.get( - `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ - start, - end, - uiFilters: '{}', - size: 5, - numBuckets: 20, - pageIndex: 0, - sortDirection: 'desc', - sortField: 'occurrences', - transactionType: 'request', - })}` - ); - - const ascendingOccurrences = ascendingResponse.body.error_groups.map( - (item: any) => item.occurrences.value - ); - - expect(ascendingOccurrences).to.eql(ascendingOccurrences.concat().sort().reverse()); - }); - - it('sorts items by the correct field', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ - start, - end, - uiFilters: '{}', - size: 5, - numBuckets: 20, - pageIndex: 0, - sortDirection: 'desc', - sortField: 'last_seen', - transactionType: 'request', - })}` - ); - - expect(response.status).to.be(200); - - const dates = response.body.error_groups.map((group: any) => group.last_seen); - - expect(dates).to.eql(dates.concat().sort().reverse()); - }); - - it('paginates through the items', async () => { - const size = 1; - - const firstPage = await supertest.get( - `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ - start, - end, - uiFilters: '{}', - size, - numBuckets: 20, - pageIndex: 0, - sortDirection: 'desc', - sortField: 'occurrences', - transactionType: 'request', - })}` - ); - - expect(firstPage.status).to.eql(200); - - const totalItems = firstPage.body.total_error_groups; - - const pages = Math.floor(totalItems / size); - - const items = await new Array(pages) - .fill(undefined) - .reduce(async (prevItemsPromise, _, pageIndex) => { - const prevItems = await prevItemsPromise; - - const thisPage = await supertest.get( - `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ - start, - end, - uiFilters: '{}', - size, - numBuckets: 20, - pageIndex, - sortDirection: 'desc', - sortField: 'occurrences', - transactionType: 'request', - })}` - ); - - return prevItems.concat(thisPage.body.error_groups); - }, Promise.resolve([])); - - expect(items.length).to.eql(totalItems); - - expect(uniqBy(items, 'group_id').length).to.eql(totalItems); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/services/__snapshots__/error_groups_comparison_statistics.snap b/x-pack/test/apm_api_integration/tests/services/__snapshots__/error_groups_comparison_statistics.snap new file mode 100644 index 00000000000000..a536a6de67ff33 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/__snapshots__/error_groups_comparison_statistics.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`APM API tests basic apm_8.0.0 Error groups comparison statistics when data is loaded returns the correct data 1`] = ` +Object { + "groupId": "051f95eabf120ebe2f8b0399fe3e54c5", + "timeseries": Array [ + Object { + "x": 1607435820000, + "y": 0, + }, + Object { + "x": 1607435880000, + "y": 0, + }, + Object { + "x": 1607435940000, + "y": 0, + }, + Object { + "x": 1607436000000, + "y": 0, + }, + Object { + "x": 1607436060000, + "y": 0, + }, + Object { + "x": 1607436120000, + "y": 0, + }, + Object { + "x": 1607436180000, + "y": 0, + }, + Object { + "x": 1607436240000, + "y": 0, + }, + Object { + "x": 1607436300000, + "y": 1, + }, + Object { + "x": 1607436360000, + "y": 0, + }, + Object { + "x": 1607436420000, + "y": 0, + }, + Object { + "x": 1607436480000, + "y": 0, + }, + Object { + "x": 1607436540000, + "y": 0, + }, + Object { + "x": 1607436600000, + "y": 1, + }, + Object { + "x": 1607436660000, + "y": 0, + }, + Object { + "x": 1607436720000, + "y": 0, + }, + Object { + "x": 1607436780000, + "y": 0, + }, + Object { + "x": 1607436840000, + "y": 0, + }, + Object { + "x": 1607436900000, + "y": 0, + }, + Object { + "x": 1607436960000, + "y": 0, + }, + Object { + "x": 1607437020000, + "y": 0, + }, + Object { + "x": 1607437080000, + "y": 0, + }, + Object { + "x": 1607437140000, + "y": 0, + }, + Object { + "x": 1607437200000, + "y": 2, + }, + Object { + "x": 1607437260000, + "y": 0, + }, + Object { + "x": 1607437320000, + "y": 1, + }, + Object { + "x": 1607437380000, + "y": 0, + }, + Object { + "x": 1607437440000, + "y": 0, + }, + Object { + "x": 1607437500000, + "y": 0, + }, + Object { + "x": 1607437560000, + "y": 0, + }, + Object { + "x": 1607437620000, + "y": 0, + }, + ], +} +`; diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups_comparison_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups_comparison_statistics.ts new file mode 100644 index 00000000000000..a13a76e2ddb463 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/error_groups_comparison_statistics.ts @@ -0,0 +1,110 @@ +/* + * 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 url from 'url'; +import expect from '@kbn/expect'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; + +type ErrorGroupsComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/comparison_statistics'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + const metadata = archives_metadata[archiveName]; + const { start, end } = metadata; + const groupIds = [ + '051f95eabf120ebe2f8b0399fe3e54c5', + '3bb34b98031a19c277bf59c3db82d3f3', + 'b1c3ff13ec52de11187facf9c6a82538', + '9581687a53eac06aba50ba17cbd959c5', + '97c2eef51fec10d177ade955670a2f15', + ]; + + registry.when( + 'Error groups comparison statistics when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles empty state', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/error_groups/comparison_statistics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + transactionType: 'request', + groupIds: JSON.stringify(groupIds), + }, + }) + ); + expect(response.status).to.be(200); + expect(response.body).to.empty(); + }); + } + ); + + registry.when( + 'Error groups comparison statistics when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + it('returns the correct data', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/error_groups/comparison_statistics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + transactionType: 'request', + groupIds: JSON.stringify(groupIds), + }, + }) + ); + + expect(response.status).to.be(200); + + const errorGroupsComparisonStatistics = response.body as ErrorGroupsComparisonStatistics; + expect(Object.keys(errorGroupsComparisonStatistics).sort()).to.eql(groupIds.sort()); + + groupIds.forEach((groupId) => { + expect(errorGroupsComparisonStatistics[groupId]).not.to.be.empty(); + }); + + const errorgroupsComparisonStatistics = errorGroupsComparisonStatistics[groupIds[0]]; + expect( + errorgroupsComparisonStatistics.timeseries.map(({ y }) => isFinite(y)).length + ).to.be.greaterThan(0); + expectSnapshot(errorgroupsComparisonStatistics).toMatch(); + }); + + it('returns an empty list when requested groupIds are not available in the given time range', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/error_groups/comparison_statistics`, + query: { + start, + end, + uiFilters: '{}', + numBuckets: 20, + transactionType: 'request', + groupIds: JSON.stringify(['foo']), + }, + }) + ); + + expect(response.status).to.be(200); + expect(response.body).to.empty(); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups_primary_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups_primary_statistics.ts new file mode 100644 index 00000000000000..8a334ca567f0ed --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/error_groups_primary_statistics.ts @@ -0,0 +1,115 @@ +/* + * 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 url from 'url'; +import expect from '@kbn/expect'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; + +type ErrorGroupsPrimaryStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/primary_statistics'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + const metadata = archives_metadata[archiveName]; + const { start, end } = metadata; + + registry.when( + 'Error groups primary statistics when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles empty state', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/error_groups/primary_statistics`, + query: { + start, + end, + uiFilters: '{}', + transactionType: 'request', + }, + }) + ); + + expect(response.status).to.be(200); + + expect(response.status).to.be(200); + expect(response.body.error_groups).to.empty(); + expect(response.body.is_aggregation_accurate).to.eql(true); + }); + } + ); + + registry.when( + 'Error groups primary statistics when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + it('returns the correct data', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/error_groups/primary_statistics`, + query: { + start, + end, + uiFilters: '{}', + transactionType: 'request', + environment: 'production', + }, + }) + ); + + expect(response.status).to.be(200); + + const errorGroupPrimaryStatistics = response.body as ErrorGroupsPrimaryStatistics; + + expect(errorGroupPrimaryStatistics.is_aggregation_accurate).to.eql(true); + expect(errorGroupPrimaryStatistics.error_groups.length).to.be.greaterThan(0); + + expectSnapshot(errorGroupPrimaryStatistics.error_groups.map(({ name }) => name)) + .toMatchInline(` + Array [ + "Could not write JSON: Null return value from advice does not match primitive return type for: public abstract double co.elastic.apm.opbeans.repositories.Numbers.getRevenue(); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Null return value from advice does not match primitive return type for: public abstract double co.elastic.apm.opbeans.repositories.Numbers.getRevenue() (through reference chain: co.elastic.apm.opbeans.repositories.Stats[\\"numbers\\"]->com.sun.proxy.$Proxy132[\\"revenue\\"])", + "java.io.IOException: Connection reset by peer", + "java.io.IOException: Connection reset by peer", + "Could not write JSON: Unable to find co.elastic.apm.opbeans.model.Customer with id 7173; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unable to find co.elastic.apm.opbeans.model.Customer with id 7173 (through reference chain: co.elastic.apm.opbeans.model.Customer_$$_jvst101_3[\\"email\\"])", + "Request method 'POST' not supported", + ] + `); + + const occurences = errorGroupPrimaryStatistics.error_groups.map( + ({ occurrences }) => occurrences + ); + + occurences.forEach((occurence) => expect(occurence).to.be.greaterThan(0)); + + expectSnapshot(occurences).toMatchInline(` + Array [ + 5, + 3, + 2, + 1, + 1, + ] + `); + + const firstItem = errorGroupPrimaryStatistics.error_groups[0]; + + expectSnapshot(firstItem).toMatchInline(` + Object { + "group_id": "051f95eabf120ebe2f8b0399fe3e54c5", + "last_seen": 1607437366098, + "name": "Could not write JSON: Null return value from advice does not match primitive return type for: public abstract double co.elastic.apm.opbeans.repositories.Numbers.getRevenue(); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Null return value from advice does not match primitive return type for: public abstract double co.elastic.apm.opbeans.repositories.Numbers.getRevenue() (through reference chain: co.elastic.apm.opbeans.repositories.Stats[\\"numbers\\"]->com.sun.proxy.$Proxy132[\\"revenue\\"])", + "occurrences": 5, + } + `); + }); + } + ); +} From 92301fe98d2681ba5c28e8857aaf73513df79dfc Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Fri, 19 Feb 2021 18:58:41 +0100 Subject: [PATCH 86/93] [ML] Fix event rate chart annotation position (#91899) --- .../event_rate_chart/event_rate_chart.tsx | 1 - .../charts/event_rate_chart/overlay_range.tsx | 31 ++++++------------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx index 93a3694ec8c21d..48f586bba8f41e 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx @@ -78,7 +78,6 @@ export const EventRateChart: FC = ({ = ({ - overlayKey, - eventRateChartData, - start, - end, - color, - showMarker = true, -}) => { - const maxHeight = Math.max(...eventRateChartData.map((e) => e.value)); - +export const OverlayRange: FC = ({ overlayKey, start, end, color, showMarker = true }) => { return ( <> = ({ coordinates: { x0: start, x1: end, - y0: 0, - y1: maxHeight, }, }, ]} @@ -62,16 +49,16 @@ export const OverlayRange: FC = ({ opacity: 0, }, }} + markerPosition={Position.Bottom} + hideTooltips={true} marker={ showMarker ? ( - <> -
    -
    - -
    -
    {timeFormatter(start)}
    +
    +
    +
    - +
    {timeFormatter(start)}
    +
    ) : undefined } /> From 4a1134c732048c08b38b138bcff1d2a3cf1f0334 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Fri, 19 Feb 2021 18:59:26 +0100 Subject: [PATCH 87/93] [Security Solution] Adds cypress-pipe (#91550) * introducing cypress-pipe * moves cypress pipe and promise packages to dev dependencies Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 5 +++-- x-pack/plugins/security_solution/cypress/support/index.js | 1 + x-pack/plugins/security_solution/cypress/tasks/timeline.ts | 5 +++-- x-pack/plugins/security_solution/cypress/tasks/timelines.ts | 6 ++---- x-pack/plugins/security_solution/cypress/tsconfig.json | 1 + yarn.lock | 5 +++++ 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 67263f53f28a24..8c8a866e9f214d 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,6 @@ "content-disposition": "0.5.3", "core-js": "^3.6.5", "custom-event-polyfill": "^0.3.0", - "cypress-promise": "^1.1.0", "cytoscape": "^3.10.0", "cytoscape-dagre": "^2.2.2", "d3-array": "1.2.4", @@ -613,6 +612,8 @@ "cypress": "^6.2.1", "cypress-cucumber-preprocessor": "^2.5.2", "cypress-multi-reporters": "^1.4.0", + "cypress-pipe": "^2.0.0", + "cypress-promise": "^1.1.0", "d3": "3.5.17", "d3-cloud": "1.2.5", "d3-scale": "1.0.7", @@ -833,8 +834,8 @@ "val-loader": "^1.1.1", "vega": "^5.19.1", "vega-lite": "^4.17.0", - "vega-spec-injector": "^0.0.2", "vega-schema-url-parser": "^2.1.0", + "vega-spec-injector": "^0.0.2", "vega-tooltip": "^0.25.0", "venn.js": "0.2.20", "vinyl-fs": "^3.0.3", diff --git a/x-pack/plugins/security_solution/cypress/support/index.js b/x-pack/plugins/security_solution/cypress/support/index.js index 0b6cea1a9487b2..73a9f1503a47de 100644 --- a/x-pack/plugins/security_solution/cypress/support/index.js +++ b/x-pack/plugins/security_solution/cypress/support/index.js @@ -22,6 +22,7 @@ // Import commands.js using ES2015 syntax: import './commands'; +import 'cypress-pipe'; Cypress.Cookies.defaults({ preserve: 'sid', diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index c001f1fc2bc47d..ada09d9c05c087 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -177,8 +177,9 @@ export const openTimelineInspectButton = () => { }; export const openTimelineFromSettings = () => { - cy.get(TIMELINE_SETTINGS_ICON).filter(':visible').click({ force: true }); - cy.get(OPEN_TIMELINE_ICON).click({ force: true }); + const click = ($el: Cypress.ObjectLike) => cy.wrap($el).click(); + cy.get(TIMELINE_SETTINGS_ICON).filter(':visible').pipe(click); + cy.get(OPEN_TIMELINE_ICON).pipe(click); }; export const openTimelineTemplateFromSettings = (id: string) => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/timelines.ts b/x-pack/plugins/security_solution/cypress/tasks/timelines.ts index 5f9448a58288b0..15575f304009b8 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timelines.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timelines.ts @@ -20,10 +20,8 @@ export const exportTimeline = (timelineId: string) => { }; export const openTimeline = (id: string) => { - // This temporary wait here is to reduce flakeyness until we integrate cypress-pipe. Then please let us use cypress pipe. - // Ref: https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/ - // Ref: https://github.com/NicholasBoll/cypress-pipe#readme - cy.get(TIMELINE(id)).should('be.visible').wait(1500).click(); + const click = ($el: Cypress.ObjectLike) => cy.wrap($el).click(); + cy.get(TIMELINE(id)).should('be.visible').pipe(click); }; export const waitForTimelinesPanelToBeLoaded = () => { diff --git a/x-pack/plugins/security_solution/cypress/tsconfig.json b/x-pack/plugins/security_solution/cypress/tsconfig.json index d0669ccb782818..270d877a362a6d 100644 --- a/x-pack/plugins/security_solution/cypress/tsconfig.json +++ b/x-pack/plugins/security_solution/cypress/tsconfig.json @@ -8,6 +8,7 @@ "tsBuildInfoFile": "../../../../build/tsbuildinfo/security_solution/cypress", "types": [ "cypress", + "cypress-pipe", "node" ], "resolveJsonModule": true, diff --git a/yarn.lock b/yarn.lock index 8a8147bd25aef2..e2c6ba8d320e60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11680,6 +11680,11 @@ cypress-multi-reporters@^1.4.0: debug "^4.1.1" lodash "^4.17.15" +cypress-pipe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cypress-pipe/-/cypress-pipe-2.0.0.tgz#577df7a70a8603d89a96dfe4092a605962181af8" + integrity sha512-KW9s+bz4tFLucH3rBGfjW+Q12n7S4QpUSSyxiGrgPOfoHlbYWzAGB3H26MO0VTojqf9NVvfd5Kt0MH5XMgbfyg== + cypress-promise@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/cypress-promise/-/cypress-promise-1.1.0.tgz#f2d66965945fe198431aaf692d5157cea9d47b25" From 4e2601d9c98a4cbf8b215d79ddadbf377e565cca Mon Sep 17 00:00:00 2001 From: Pete Hampton Date: Fri, 19 Feb 2021 18:10:15 +0000 Subject: [PATCH 88/93] [7.12][Telemetry] Add missing fields for security telemetry (#91920) Co-authored-by: Thiago Souza --- .../server/lib/telemetry/sender.test.ts | 16 ++++ .../server/lib/telemetry/sender.ts | 80 +++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts index 56e2f9c7c73043..d5edd4678a9a22 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts @@ -34,6 +34,11 @@ describe('TelemetryEventsSender', () => { agent: { name: 'test', }, + rule: { + id: 'X', + name: 'Y', + ruleset: 'Z', + }, file: { size: 3, path: 'X', @@ -47,6 +52,9 @@ describe('TelemetryEventsSender', () => { malware_classification: { key1: 'X', }, + malware_signature: { + key1: 'X', + }, quarantine_result: true, quarantine_message: 'this file is bad', something_else: 'nope', @@ -70,6 +78,11 @@ describe('TelemetryEventsSender', () => { agent: { name: 'test', }, + rule: { + id: 'X', + name: 'Y', + ruleset: 'Z', + }, file: { size: 3, path: 'X', @@ -81,6 +94,9 @@ describe('TelemetryEventsSender', () => { malware_classification: { key1: 'X', }, + malware_signature: { + key1: 'X', + }, quarantine_result: true, quarantine_message: 'this file is bad', }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts index a18604fb92a40a..3ee18a84e11333 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts @@ -296,16 +296,20 @@ interface AllowlistFields { // Allow list for the data we include in the events. True means that it is deep-cloned // blindly. Object contents means that we only copy the fields that appear explicitly in // the sub-object. +/* eslint-disable @typescript-eslint/naming-convention */ const allowlistEventFields: AllowlistFields = { '@timestamp': true, agent: true, Endpoint: true, + Memory_protection: true, Ransomware: true, data_stream: true, ecs: true, elastic: true, event: true, rule: { + id: true, + name: true, ruleset: true, }, file: { @@ -320,6 +324,7 @@ const allowlistEventFields: AllowlistFields = { Ext: { code_signature: true, malware_classification: true, + malware_signature: true, quarantine_result: true, quarantine_message: true, }, @@ -335,7 +340,12 @@ const allowlistEventFields: AllowlistFields = { pid: true, uptime: true, Ext: { + architecture: true, code_signature: true, + dll: true, + token: { + integrity_level_name: true, + }, }, parent: { name: true, @@ -343,12 +353,82 @@ const allowlistEventFields: AllowlistFields = { command_line: true, hash: true, Ext: { + architecture: true, code_signature: true, + dll: true, + token: { + integrity_level_name: true, + }, }, uptime: true, pid: true, ppid: true, }, + Target: { + process: { + Ext: { + architecture: true, + code_signature: true, + dll: true, + token: { + integrity_level_name: true, + }, + }, + parent: { + process: { + Ext: { + architecture: true, + code_signature: true, + dll: true, + token: { + integrity_level_name: true, + }, + }, + }, + }, + thread: { + Ext: { + call_stack: true, + start_address: true, + start_address_details: { + address_offset: true, + allocation_base: true, + allocation_protection: true, + allocation_size: true, + allocation_type: true, + base_address: true, + bytes_start_address: true, + compressed_bytes: true, + dest_bytes: true, + dest_bytes_disasm: true, + dest_bytes_disasm_hash: true, + pe: { + Ext: { + legal_copyright: true, + product_version: true, + code_signature: { + status: true, + subject_name: true, + trusted: true, + }, + company: true, + description: true, + file_version: true, + imphash: true, + original_file_name: true, + product: true, + }, + }, + pe_detected: true, + region_protection: true, + region_size: true, + region_state: true, + strings: true, + }, + }, + }, + }, + }, token: { integrity_level_name: true, }, From fb2f6abed20c32a7f45b5c804aacba3208ea15bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Fri, 19 Feb 2021 18:59:03 +0000 Subject: [PATCH 89/93] Add `@kbn/analytics` to UI Shared Deps (#91810) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-analytics/src/index.ts | 13 +++++++++---- packages/kbn-analytics/src/metrics/index.ts | 14 +++++++++----- packages/kbn-ui-shared-deps/entry.js | 1 + packages/kbn-ui-shared-deps/index.js | 1 + 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/kbn-analytics/src/index.ts b/packages/kbn-analytics/src/index.ts index 3f6dfdf6a401ab..a546fb9d97e429 100644 --- a/packages/kbn-analytics/src/index.ts +++ b/packages/kbn-analytics/src/index.ts @@ -6,8 +6,13 @@ * Side Public License, v 1. */ -export { ReportHTTP, Reporter, ReporterConfig } from './reporter'; -export { UiCounterMetricType, METRIC_TYPE } from './metrics'; -export { Report, ReportManager } from './report'; +// Export types separately to the actual run-time objects +export type { ReportHTTP, ReporterConfig } from './reporter'; +export type { UiCounterMetricType } from './metrics'; +export type { Report } from './report'; +export type { Storage } from './storage'; + +export { Reporter } from './reporter'; +export { METRIC_TYPE } from './metrics'; +export { ReportManager } from './report'; export { ApplicationUsageTracker } from './application_usage_tracker'; -export { Storage } from './storage'; diff --git a/packages/kbn-analytics/src/metrics/index.ts b/packages/kbn-analytics/src/metrics/index.ts index dc03545a5ff3c0..aacc3b398a16c6 100644 --- a/packages/kbn-analytics/src/metrics/index.ts +++ b/packages/kbn-analytics/src/metrics/index.ts @@ -6,13 +6,17 @@ * Side Public License, v 1. */ -import { UiCounterMetric } from './ui_counter'; -import { UserAgentMetric } from './user_agent'; -import { ApplicationUsageMetric } from './application_usage'; +import type { UiCounterMetric } from './ui_counter'; +import type { UserAgentMetric } from './user_agent'; +import type { ApplicationUsageMetric } from './application_usage'; -export { UiCounterMetric, createUiCounterMetric, UiCounterMetricType } from './ui_counter'; +// Export types separately to the actual run-time objects +export type { ApplicationUsageMetric } from './application_usage'; +export type { UiCounterMetric, UiCounterMetricType } from './ui_counter'; + +export { createUiCounterMetric } from './ui_counter'; export { trackUsageAgent } from './user_agent'; -export { createApplicationUsageMetric, ApplicationUsageMetric } from './application_usage'; +export { createApplicationUsageMetric } from './application_usage'; export type Metric = UiCounterMetric | UserAgentMetric | ApplicationUsageMetric; export enum METRIC_TYPE { diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/entry.js index c02489afe7bc2d..ede617908fd3d9 100644 --- a/packages/kbn-ui-shared-deps/entry.js +++ b/packages/kbn-ui-shared-deps/entry.js @@ -46,3 +46,4 @@ export const LodashFp = require('lodash/fp'); // runtime deps which don't need to be copied across all bundles export const TsLib = require('tslib'); +export const KbnAnalytics = require('@kbn/analytics'); diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js index 79b4bde7878514..d1217dd8db0d4c 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/index.js @@ -57,5 +57,6 @@ exports.externals = { * runtime deps which don't need to be copied across all bundles */ tslib: '__kbnSharedDeps__.TsLib', + '@kbn/analytics': '__kbnSharedDeps__.KbnAnalytics', }; exports.publicPathLoader = require.resolve('./public_path_loader'); From fc77586b5ae0e0abe97cecb3824e065fed47073c Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Fri, 19 Feb 2021 12:41:55 -0700 Subject: [PATCH 90/93] [Security Solution] Clearing up all jest errors and warnings (#91740) --- .../markdown_editor/plugins/index.ts | 7 +++-- .../components/query_bar/index.test.tsx | 31 ++++++++++++------- .../all/exceptions/exceptions_table.test.tsx | 8 +++++ .../pages/endpoint_hosts/view/index.test.tsx | 10 ++++++ .../policy/view/policy_forms/locked_card.tsx | 24 +++++++++----- .../components/flyout/header/index.test.tsx | 8 +++++ .../components/open_timeline/helpers.test.ts | 11 +++---- .../components/side_panel/index.test.tsx | 8 +++++ .../components/timeline/index.test.tsx | 8 +++++ 9 files changed, 87 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts index 7564c246513def..bc0da84133e689 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts @@ -6,13 +6,16 @@ */ import { + EuiMarkdownEditorUiPlugin, getDefaultEuiMarkdownParsingPlugins, getDefaultEuiMarkdownProcessingPlugins, + getDefaultEuiMarkdownUiPlugins, } from '@elastic/eui'; import * as timelineMarkdownPlugin from './timeline'; - -export const uiPlugins = [timelineMarkdownPlugin.plugin]; +const uiPlugins: EuiMarkdownEditorUiPlugin[] = getDefaultEuiMarkdownUiPlugins(); +uiPlugins.push(timelineMarkdownPlugin.plugin); +export { uiPlugins }; export const parsingPlugins = getDefaultEuiMarkdownParsingPlugins(); export const processingPlugins = getDefaultEuiMarkdownProcessingPlugins(); diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx index fbf56bd2357891..1e998f9798e972 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx @@ -34,7 +34,16 @@ describe('QueryBar ', () => { await waitFor(() => getByTestId('queryInput')); // check for presence of query input return mount(Component); }; + let abortSpy: jest.SpyInstance; + beforeAll(() => { + const mockAbort = new AbortController(); + mockAbort.abort(); + abortSpy = jest.spyOn(window, 'AbortController').mockImplementation(() => mockAbort); + }); + afterAll(() => { + abortSpy.mockRestore(); + }); beforeEach(() => { mockOnChangeQuery.mockClear(); mockOnSubmitQuery.mockClear(); @@ -264,7 +273,6 @@ describe('QueryBar ', () => { const onChangedQueryRef = searchBarProps.onQueryChange; const onSubmitQueryRef = searchBarProps.onQuerySubmit; const onSavedQueryRef = searchBarProps.onSavedQueryUpdated; - wrapper.setProps({ onSavedQuery: jest.fn() }); wrapper.update(); @@ -294,22 +302,21 @@ describe('QueryBar ', () => { onSavedQuery={mockOnSavedQuery} /> ); - await waitFor(() => { - const isSavedQueryPopoverOpen = () => - wrapper.find('EuiPopover[id="savedQueryPopover"]').prop('isOpen'); + const isSavedQueryPopoverOpen = () => + wrapper.find('EuiPopover[id="savedQueryPopover"]').prop('isOpen'); - expect(isSavedQueryPopoverOpen()).toBeFalsy(); + expect(isSavedQueryPopoverOpen()).toBeFalsy(); - wrapper - .find('button[data-test-subj="saved-query-management-popover-button"]') - .simulate('click'); + wrapper + .find('button[data-test-subj="saved-query-management-popover-button"]') + .simulate('click'); + await waitFor(() => { expect(isSavedQueryPopoverOpen()).toBeTruthy(); + }); + wrapper.find('button[data-test-subj="saved-query-management-save-button"]').simulate('click'); - wrapper - .find('button[data-test-subj="saved-query-management-save-button"]') - .simulate('click'); - + await waitFor(() => { expect(isSavedQueryPopoverOpen()).toBeFalsy(); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx index 3b87c786d0e368..88b42c506dabc0 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx @@ -20,7 +20,15 @@ import { useAllExceptionLists } from './use_all_exception_lists'; jest.mock('../../../../../../common/lib/kibana'); jest.mock('./use_all_exception_lists'); jest.mock('../../../../../../shared_imports'); +jest.mock('@kbn/i18n/react', () => { + const originalModule = jest.requireActual('@kbn/i18n/react'); + const FormattedRelative = jest.fn().mockImplementation(() => '20 hours ago'); + return { + ...originalModule, + FormattedRelative, + }; +}); describe('ExceptionListsTable', () => { const exceptionList1 = getExceptionListSchemaMock(); const exceptionList2 = { ...getExceptionListSchemaMock(), list_id: 'not_endpoint_list', id: '2' }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 9925b35616c918..79e91fdeb813af 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -54,6 +54,16 @@ describe('when on the list page', () => { let store: AppContextTestRender['store']; let coreStart: AppContextTestRender['coreStart']; let middlewareSpy: AppContextTestRender['middlewareSpy']; + let abortSpy: jest.SpyInstance; + beforeAll(() => { + const mockAbort = new AbortController(); + mockAbort.abort(); + abortSpy = jest.spyOn(window, 'AbortController').mockImplementation(() => mockAbort); + }); + + afterAll(() => { + abortSpy.mockRestore(); + }); beforeEach(() => { const mockedContext = createAppRootMockRenderer(); ({ history, store, coreStart, middlewareSpy } = mockedContext); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/locked_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/locked_card.tsx index 5c19a103076084..e9e9195b819d34 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/locked_card.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/locked_card.tsx @@ -6,7 +6,15 @@ */ import React, { memo } from 'react'; -import { EuiCard, EuiIcon, EuiTextColor, EuiLink, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiCard, + EuiIcon, + EuiTextColor, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiText, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; @@ -42,8 +50,10 @@ export const LockedPolicyCard = memo(() => { } - description={ - + description={false} + > + +

    @@ -59,7 +69,7 @@ export const LockedPolicyCard = memo(() => { @@ -73,9 +83,9 @@ export const LockedPolicyCard = memo(() => { />

    - - } - /> + + + ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx index 6713be176586cc..68b4f2e4a0c31c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx @@ -26,7 +26,15 @@ jest.mock('../../../containers/kpis', () => ({ })); const useKibanaMock = useKibana as jest.Mocked; jest.mock('../../../../common/lib/kibana'); +jest.mock('@kbn/i18n/react', () => { + const originalModule = jest.requireActual('@kbn/i18n/react'); + const FormattedRelative = jest.fn().mockImplementation(() => '20 hours ago'); + return { + ...originalModule, + FormattedRelative, + }; +}); const mockUseTimelineKpiResponse = { processCount: 1, userCount: 1, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index 5ae5237421b541..e62b19ce599f69 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -9,11 +9,7 @@ import { cloneDeep, getOr, omit } from 'lodash/fp'; import { Dispatch } from 'redux'; import ApolloClient from 'apollo-client'; -import { - mockTimelineResults, - mockTimelineResult, - mockTimelineModel, -} from '../../../common/mock/timeline_results'; +import { mockTimelineResults, mockTimelineResult, mockTimelineModel } from '../../../common/mock'; import { timelineDefaults } from '../../store/timeline/defaults'; import { setTimelineRangeDatePicker as dispatchSetTimelineRangeDatePicker } from '../../../common/store/inputs/actions'; import { @@ -37,7 +33,7 @@ import { formatTimelineResultToModel, } from './helpers'; import { OpenTimelineResult, DispatchUpdateTimeline } from './types'; -import { KueryFilterQueryKind } from '../../../common/store/model'; +import { KueryFilterQueryKind } from '../../../common/store'; import { Note } from '../../../common/lib/note'; import moment from 'moment'; import sinon from 'sinon'; @@ -1275,7 +1271,7 @@ describe('helpers', () => { describe('update a timeline', () => { const updateIsLoading = jest.fn(); - const updateTimeline = jest.fn(); + const updateTimeline = jest.fn().mockImplementation(() => jest.fn()); const selectedTimeline = { ...mockSelectedTimeline }; const apolloClient = { query: (jest.fn().mockResolvedValue(selectedTimeline) as unknown) as ApolloClient<{}>, @@ -1316,6 +1312,7 @@ describe('helpers', () => { args.duplicate, args.timelineType ); + expect(updateTimeline).toBeCalledWith({ timeline: { ...timeline, diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx index 71ab7f01ddd546..15b2b33409707c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx @@ -21,6 +21,14 @@ import { createStore, State } from '../../../common/store'; import { DetailsPanel } from './index'; import { TimelineExpandedDetail, TimelineTabs } from '../../../../common/types/timeline'; import { FlowTarget } from '../../../../common/search_strategy/security_solution/network'; +jest.mock('react-apollo', () => { + const original = jest.requireActual('react-apollo'); + return { + ...original, + // eslint-disable-next-line react/display-name + Query: () => <>, + }; +}); describe('Details Panel Component', () => { const state: State = { ...mockGlobalState }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx index e7422e32805a9b..ee2ce8cf8103b5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx @@ -25,7 +25,15 @@ jest.mock('../../containers/index', () => ({ jest.mock('../../../common/lib/kibana'); jest.mock('../../../common/components/url_state/normalize_time_range.ts'); +jest.mock('@kbn/i18n/react', () => { + const originalModule = jest.requireActual('@kbn/i18n/react'); + const FormattedRelative = jest.fn().mockImplementation(() => '20 hours ago'); + return { + ...originalModule, + FormattedRelative, + }; +}); const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock; jest.mock('use-resize-observer/polyfilled'); mockUseResizeObserver.mockImplementation(() => ({})); From a6a567f47658df3e8ff2fd7b0c0e27f59fcf3de1 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Fri, 19 Feb 2021 11:56:09 -0800 Subject: [PATCH 91/93] Use documentation link service for snapshot restore (#91596) Co-authored-by: Alison Goryachev --- ...-plugin-core-public.doclinksstart.links.md | 5 ++ ...kibana-plugin-core-public.doclinksstart.md | 2 +- .../public/doc_links/doc_links_service.ts | 26 ++++++ src/core/public/public.api.md | 5 ++ .../helpers/setup_environment.tsx | 4 +- .../policy_form/steps/step_logistics.tsx | 13 ++- .../policy_form/steps/step_retention.tsx | 5 +- .../steps/step_settings/step_settings.tsx | 5 +- .../components/repository_form/step_one.tsx | 11 ++- .../components/repository_form/step_two.tsx | 6 +- .../data_streams_global_state_call_out.tsx | 10 +-- .../steps/step_logistics/step_logistics.tsx | 7 +- .../steps/step_settings.tsx | 8 +- .../retention_update_modal_provider.tsx | 6 +- .../public/application/constants/index.ts | 12 --- .../public/application/lib/type_to_doc_url.ts | 31 ++++++++ .../application/mount_management_section.ts | 3 - .../public/application/sections/home/home.tsx | 6 +- .../repository_details/repository_details.tsx | 7 +- .../home/snapshot_list/snapshot_list.tsx | 8 +- .../documentation/documentation_links.ts | 79 ------------------- .../services/documentation/index.ts | 8 -- 22 files changed, 117 insertions(+), 150 deletions(-) create mode 100644 x-pack/plugins/snapshot_restore/public/application/lib/type_to_doc_url.ts delete mode 100644 x-pack/plugins/snapshot_restore/public/application/services/documentation/documentation_links.ts delete mode 100644 x-pack/plugins/snapshot_restore/public/application/services/documentation/index.ts diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 54c065480b113c..026032a7b07409 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -117,6 +117,7 @@ readonly links: { }; readonly date: { readonly dateMath: string; + readonly dateMathIndexNames: string; }; readonly management: Record; readonly ml: Record; @@ -130,6 +131,7 @@ readonly links: { createApiKey: string; createPipeline: string; createTransformRequest: string; + cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; @@ -137,6 +139,7 @@ readonly links: { painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; + putSnapshotLifecyclePolicy: string; putWatch: string; updateTransform: string; }>; @@ -158,5 +161,7 @@ readonly links: { }>; readonly watcher: Record; readonly ccs: Record; + readonly plugins: Record; + readonly snapshotRestore: Record; }; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 0bca16a0bb7107..d653623d5fe225 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
    readonly dashboard: {
    readonly guide: string;
    readonly drilldowns: string;
    readonly drilldownsTriggerPicker: string;
    readonly urlDrilldownTemplateSyntax: string;
    readonly urlDrilldownVariables: string;
    };
    readonly discover: Record<string, string>;
    readonly filebeat: {
    readonly base: string;
    readonly installation: string;
    readonly configuration: string;
    readonly elasticsearchOutput: string;
    readonly elasticsearchModule: string;
    readonly startup: string;
    readonly exportedFields: string;
    };
    readonly auditbeat: {
    readonly base: string;
    };
    readonly metricbeat: {
    readonly base: string;
    readonly configure: string;
    readonly httpEndpoint: string;
    readonly install: string;
    readonly start: string;
    };
    readonly enterpriseSearch: {
    readonly base: string;
    readonly appSearchBase: string;
    readonly workplaceSearchBase: string;
    };
    readonly heartbeat: {
    readonly base: string;
    };
    readonly logstash: {
    readonly base: string;
    };
    readonly functionbeat: {
    readonly base: string;
    };
    readonly winlogbeat: {
    readonly base: string;
    };
    readonly aggs: {
    readonly composite: string;
    readonly composite_missing_bucket: string;
    readonly date_histogram: string;
    readonly date_range: string;
    readonly date_format_pattern: string;
    readonly filter: string;
    readonly filters: string;
    readonly geohash_grid: string;
    readonly histogram: string;
    readonly ip_range: string;
    readonly range: string;
    readonly significant_terms: string;
    readonly terms: string;
    readonly avg: string;
    readonly avg_bucket: string;
    readonly max_bucket: string;
    readonly min_bucket: string;
    readonly sum_bucket: string;
    readonly cardinality: string;
    readonly count: string;
    readonly cumulative_sum: string;
    readonly derivative: string;
    readonly geo_bounds: string;
    readonly geo_centroid: string;
    readonly max: string;
    readonly median: string;
    readonly min: string;
    readonly moving_avg: string;
    readonly percentile_ranks: string;
    readonly serial_diff: string;
    readonly std_dev: string;
    readonly sum: string;
    readonly top_hits: string;
    };
    readonly runtimeFields: string;
    readonly scriptedFields: {
    readonly scriptFields: string;
    readonly scriptAggs: string;
    readonly painless: string;
    readonly painlessApi: string;
    readonly painlessLangSpec: string;
    readonly painlessSyntax: string;
    readonly painlessWalkthrough: string;
    readonly luceneExpressions: string;
    };
    readonly indexPatterns: {
    readonly loadingData: string;
    readonly introduction: string;
    };
    readonly addData: string;
    readonly kibana: string;
    readonly elasticsearch: Record<string, string>;
    readonly siem: {
    readonly guide: string;
    readonly gettingStarted: string;
    };
    readonly query: {
    readonly eql: string;
    readonly luceneQuerySyntax: string;
    readonly queryDsl: string;
    readonly kueryQuerySyntax: string;
    };
    readonly date: {
    readonly dateMath: string;
    };
    readonly management: Record<string, string>;
    readonly ml: Record<string, string>;
    readonly transforms: Record<string, string>;
    readonly visualize: Record<string, string>;
    readonly apis: Readonly<{
    createIndex: string;
    createSnapshotLifecyclePolicy: string;
    createRoleMapping: string;
    createRoleMappingTemplates: string;
    createApiKey: string;
    createPipeline: string;
    createTransformRequest: string;
    executeWatchActionModes: string;
    indexExists: string;
    openIndex: string;
    putComponentTemplate: string;
    painlessExecute: string;
    painlessExecuteAPIContexts: string;
    putComponentTemplateMetadata: string;
    putWatch: string;
    updateTransform: string;
    }>;
    readonly observability: Record<string, string>;
    readonly alerting: Record<string, string>;
    readonly maps: Record<string, string>;
    readonly monitoring: Record<string, string>;
    readonly security: Readonly<{
    apiKeyServiceSettings: string;
    clusterPrivileges: string;
    elasticsearchSettings: string;
    elasticsearchEnableSecurity: string;
    indicesPrivileges: string;
    kibanaTLS: string;
    kibanaPrivileges: string;
    mappingRoles: string;
    mappingRolesFieldRules: string;
    runAsPrivilege: string;
    }>;
    readonly watcher: Record<string, string>;
    readonly ccs: Record<string, string>;
    } | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
    readonly dashboard: {
    readonly guide: string;
    readonly drilldowns: string;
    readonly drilldownsTriggerPicker: string;
    readonly urlDrilldownTemplateSyntax: string;
    readonly urlDrilldownVariables: string;
    };
    readonly discover: Record<string, string>;
    readonly filebeat: {
    readonly base: string;
    readonly installation: string;
    readonly configuration: string;
    readonly elasticsearchOutput: string;
    readonly elasticsearchModule: string;
    readonly startup: string;
    readonly exportedFields: string;
    };
    readonly auditbeat: {
    readonly base: string;
    };
    readonly metricbeat: {
    readonly base: string;
    readonly configure: string;
    readonly httpEndpoint: string;
    readonly install: string;
    readonly start: string;
    };
    readonly enterpriseSearch: {
    readonly base: string;
    readonly appSearchBase: string;
    readonly workplaceSearchBase: string;
    };
    readonly heartbeat: {
    readonly base: string;
    };
    readonly logstash: {
    readonly base: string;
    };
    readonly functionbeat: {
    readonly base: string;
    };
    readonly winlogbeat: {
    readonly base: string;
    };
    readonly aggs: {
    readonly composite: string;
    readonly composite_missing_bucket: string;
    readonly date_histogram: string;
    readonly date_range: string;
    readonly date_format_pattern: string;
    readonly filter: string;
    readonly filters: string;
    readonly geohash_grid: string;
    readonly histogram: string;
    readonly ip_range: string;
    readonly range: string;
    readonly significant_terms: string;
    readonly terms: string;
    readonly avg: string;
    readonly avg_bucket: string;
    readonly max_bucket: string;
    readonly min_bucket: string;
    readonly sum_bucket: string;
    readonly cardinality: string;
    readonly count: string;
    readonly cumulative_sum: string;
    readonly derivative: string;
    readonly geo_bounds: string;
    readonly geo_centroid: string;
    readonly max: string;
    readonly median: string;
    readonly min: string;
    readonly moving_avg: string;
    readonly percentile_ranks: string;
    readonly serial_diff: string;
    readonly std_dev: string;
    readonly sum: string;
    readonly top_hits: string;
    };
    readonly runtimeFields: string;
    readonly scriptedFields: {
    readonly scriptFields: string;
    readonly scriptAggs: string;
    readonly painless: string;
    readonly painlessApi: string;
    readonly painlessLangSpec: string;
    readonly painlessSyntax: string;
    readonly painlessWalkthrough: string;
    readonly luceneExpressions: string;
    };
    readonly indexPatterns: {
    readonly loadingData: string;
    readonly introduction: string;
    };
    readonly addData: string;
    readonly kibana: string;
    readonly elasticsearch: Record<string, string>;
    readonly siem: {
    readonly guide: string;
    readonly gettingStarted: string;
    };
    readonly query: {
    readonly eql: string;
    readonly luceneQuerySyntax: string;
    readonly queryDsl: string;
    readonly kueryQuerySyntax: string;
    };
    readonly date: {
    readonly dateMath: string;
    readonly dateMathIndexNames: string;
    };
    readonly management: Record<string, string>;
    readonly ml: Record<string, string>;
    readonly transforms: Record<string, string>;
    readonly visualize: Record<string, string>;
    readonly apis: Readonly<{
    createIndex: string;
    createSnapshotLifecyclePolicy: string;
    createRoleMapping: string;
    createRoleMappingTemplates: string;
    createApiKey: string;
    createPipeline: string;
    createTransformRequest: string;
    cronExpressions: string;
    executeWatchActionModes: string;
    indexExists: string;
    openIndex: string;
    putComponentTemplate: string;
    painlessExecute: string;
    painlessExecuteAPIContexts: string;
    putComponentTemplateMetadata: string;
    putSnapshotLifecyclePolicy: string;
    putWatch: string;
    updateTransform: string;
    }>;
    readonly observability: Record<string, string>;
    readonly alerting: Record<string, string>;
    readonly maps: Record<string, string>;
    readonly monitoring: Record<string, string>;
    readonly security: Readonly<{
    apiKeyServiceSettings: string;
    clusterPrivileges: string;
    elasticsearchSettings: string;
    elasticsearchEnableSecurity: string;
    indicesPrivileges: string;
    kibanaTLS: string;
    kibanaPrivileges: string;
    mappingRoles: string;
    mappingRolesFieldRules: string;
    runAsPrivilege: string;
    }>;
    readonly watcher: Record<string, string>;
    readonly ccs: Record<string, string>;
    readonly plugins: Record<string, string>;
    readonly snapshotRestore: Record<string, string>;
    } | | diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 77792286d6839f..23534b3cf92103 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -20,6 +20,7 @@ export class DocLinksService { const DOC_LINK_VERSION = injectedMetadata.getKibanaBranch(); const ELASTIC_WEBSITE_URL = 'https://www.elastic.co/'; const ELASTICSEARCH_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`; + const PLUGIN_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`; return deepFreeze({ DOC_LINK_VERSION, @@ -126,6 +127,7 @@ export class DocLinksService { addData: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/connect-to-elasticsearch.html`, kibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index.html`, elasticsearch: { + indexModules: `${ELASTICSEARCH_DOCS}index-modules.html`, mapping: `${ELASTICSEARCH_DOCS}mapping.html`, remoteClusters: `${ELASTICSEARCH_DOCS}modules-remote-clusters.html`, remoteClustersProxy: `${ELASTICSEARCH_DOCS}modules-remote-clusters.html#proxy-mode`, @@ -145,6 +147,7 @@ export class DocLinksService { }, date: { dateMath: `${ELASTICSEARCH_DOCS}common-options.html#date-math`, + dateMathIndexNames: `${ELASTICSEARCH_DOCS}date-math-index-names.html`, }, management: { kibanaSearchSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-search-settings`, @@ -239,6 +242,7 @@ export class DocLinksService { createApiKey: `${ELASTICSEARCH_DOCS}security-api-create-api-key.html`, createPipeline: `${ELASTICSEARCH_DOCS}put-pipeline-api.html`, createTransformRequest: `${ELASTICSEARCH_DOCS}put-transform.html#put-transform-request-body`, + cronExpressions: `${ELASTICSEARCH_DOCS}cron-expressions.html`, executeWatchActionModes: `${ELASTICSEARCH_DOCS}watcher-api-execute-watch.html#watcher-api-execute-watch-action-mode`, indexExists: `${ELASTICSEARCH_DOCS}indices-exists.html`, openIndex: `${ELASTICSEARCH_DOCS}indices-open-close.html`, @@ -246,9 +250,26 @@ export class DocLinksService { painlessExecute: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/painless/${DOC_LINK_VERSION}/painless-execute-api.html`, painlessExecuteAPIContexts: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/painless/${DOC_LINK_VERSION}/painless-execute-api.html#_contexts`, putComponentTemplateMetadata: `${ELASTICSEARCH_DOCS}indices-component-template.html#component-templates-metadata`, + putSnapshotLifecyclePolicy: `${ELASTICSEARCH_DOCS}slm-api-put-policy.html`, putWatch: `${ELASTICSEARCH_DOCS}/watcher-api-put-watch.html`, updateTransform: `${ELASTICSEARCH_DOCS}update-transform.html`, }, + plugins: { + azureRepo: `${PLUGIN_DOCS}repository-azure.html`, + gcsRepo: `${PLUGIN_DOCS}repository-gcs.html`, + hdfsRepo: `${PLUGIN_DOCS}repository-hdfs.html`, + s3Repo: `${PLUGIN_DOCS}repository-s3.html`, + snapshotRestoreRepos: `${PLUGIN_DOCS}repository.html`, + }, + snapshotRestore: { + guide: `${ELASTICSEARCH_DOCS}snapshot-restore.html`, + changeIndexSettings: `${ELASTICSEARCH_DOCS}snapshots-restore-snapshot.html#change-index-settings-during-restore`, + createSnapshot: `${ELASTICSEARCH_DOCS}snapshots-take-snapshot.html`, + registerSharedFileSystem: `${ELASTICSEARCH_DOCS}snapshots-register-repository.html#snapshots-filesystem-repository`, + registerSourceOnly: `${ELASTICSEARCH_DOCS}snapshots-register-repository.html#snapshots-source-only-repository`, + registerUrl: `${ELASTICSEARCH_DOCS}snapshots-register-repository.html#snapshots-read-only-repository`, + restoreSnapshot: `${ELASTICSEARCH_DOCS}snapshots-restore-snapshot.html`, + }, }, }); } @@ -368,6 +389,7 @@ export interface DocLinksStart { }; readonly date: { readonly dateMath: string; + readonly dateMathIndexNames: string; }; readonly management: Record; readonly ml: Record; @@ -381,6 +403,7 @@ export interface DocLinksStart { createApiKey: string; createPipeline: string; createTransformRequest: string; + cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; @@ -388,6 +411,7 @@ export interface DocLinksStart { painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; + putSnapshotLifecyclePolicy: string; putWatch: string; updateTransform: string; }>; @@ -409,5 +433,7 @@ export interface DocLinksStart { }>; readonly watcher: Record; readonly ccs: Record; + readonly plugins: Record; + readonly snapshotRestore: Record; }; } diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index e29173d1495af7..b068606b880472 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -587,6 +587,7 @@ export interface DocLinksStart { }; readonly date: { readonly dateMath: string; + readonly dateMathIndexNames: string; }; readonly management: Record; readonly ml: Record; @@ -600,6 +601,7 @@ export interface DocLinksStart { createApiKey: string; createPipeline: string; createTransformRequest: string; + cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; @@ -607,6 +609,7 @@ export interface DocLinksStart { painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; + putSnapshotLifecyclePolicy: string; putWatch: string; updateTransform: string; }>; @@ -628,6 +631,8 @@ export interface DocLinksStart { }>; readonly watcher: Record; readonly ccs: Record; + readonly plugins: Record; + readonly snapshotRestore: Record; }; } diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx index 83003962f473b3..3f066875e880c2 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx @@ -21,7 +21,6 @@ import { AppContextProvider } from '../../../public/application/app_context'; import { textService } from '../../../public/application/services/text'; import { init as initHttpRequests } from './http_requests'; import { UiMetricService } from '../../../public/application/services'; -import { documentationLinksService } from '../../../public/application/services/documentation'; const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); @@ -40,7 +39,7 @@ export const services = { setUiMetricService(services.uiMetricService); const appDependencies = { - core: coreMock.createSetup(), + core: coreMock.createStart(), services, config: { slm_ui: { enabled: true }, @@ -53,7 +52,6 @@ export const setupEnvironment = () => { httpService.setup(mockHttpClient); breadcrumbService.setup(() => undefined); textService.setup(i18n); - documentationLinksService.setup({} as any); docTitleService.setup(() => undefined); const { server, httpRequestsMockHelpers } = initHttpRequests(); diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx index f09812011f035b..5545e8a87d99d0 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx @@ -26,11 +26,10 @@ import { import { Repository } from '../../../../../common/types'; import { Frequency, CronEditor, SectionError } from '../../../../shared_imports'; -import { useServices } from '../../../app_context'; +import { useCore, useServices } from '../../../app_context'; import { DEFAULT_POLICY_SCHEDULE, DEFAULT_POLICY_FREQUENCY } from '../../../constants'; import { useLoadRepositories } from '../../../services/http'; import { linkToAddRepository } from '../../../services/navigation'; -import { documentationLinksService } from '../../../services/documentation'; import { SectionLoading } from '../../'; import { StepProps } from './'; @@ -57,6 +56,7 @@ export const PolicyStepLogistics: React.FunctionComponent = ({ } = useLoadRepositories(); const { i18n, history } = useServices(); + const { docLinks } = useCore(); const [showRepositoryNotFoundWarning, setShowRepositoryNotFoundWarning] = useState( false @@ -338,10 +338,7 @@ export const PolicyStepLogistics: React.FunctionComponent = ({ defaultMessage="Supports date math expressions. {docLink}" values={{ docLink: ( - + = ({ defaultMessage="Use cron expression. {docLink}" values={{ docLink: ( - + = ({ diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx index 15da65443ceb8e..62f38ce9952dfe 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_retention.tsx @@ -21,9 +21,9 @@ import { import { SlmPolicyPayload } from '../../../../../common/types'; import { TIME_UNITS } from '../../../../../common/constants'; -import { documentationLinksService } from '../../../services/documentation'; import { StepProps } from './'; import { textService } from '../../../services/text'; +import { useCore } from '../../../app_context'; const getExpirationTimeOptions = (unitSize = '0') => Object.entries(TIME_UNITS).map(([_key, value]) => ({ @@ -37,6 +37,7 @@ export const PolicyStepRetention: React.FunctionComponent = ({ errors, }) => { const { retention = {} } = policy; + const { docLinks } = useCore(); const updatePolicyRetention = ( updatedFields: Partial, @@ -224,7 +225,7 @@ export const PolicyStepRetention: React.FunctionComponent = ({ diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx index 94b296dcf9c04f..dcaad024eb0f75 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/step_settings.tsx @@ -19,10 +19,10 @@ import { } from '@elastic/eui'; import { SlmPolicyPayload } from '../../../../../../common/types'; -import { documentationLinksService } from '../../../../services/documentation'; import { StepProps } from '../'; import { IndicesAndDataStreamsField } from './fields'; +import { useCore } from '../../../../app_context'; export const PolicyStepSettings: React.FunctionComponent = ({ policy, @@ -31,6 +31,7 @@ export const PolicyStepSettings: React.FunctionComponent = ({ updatePolicy, errors, }) => { + const { docLinks } = useCore(); const { config = {}, isManagedPolicy } = policy; const updatePolicyConfig = ( @@ -184,7 +185,7 @@ export const PolicyStepSettings: React.FunctionComponent = ({ diff --git a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/step_one.tsx b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/step_one.tsx index 6e072a6fac7515..91802c6bcf1fa2 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/repository_form/step_one.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/repository_form/step_one.tsx @@ -28,11 +28,12 @@ import { Repository, RepositoryType, EmptyRepository } from '../../../../common/ import { REPOSITORY_TYPES } from '../../../../common'; import { SectionError, Error } from '../../../shared_imports'; -import { documentationLinksService } from '../../services/documentation'; import { useLoadRepositoryTypes } from '../../services/http'; import { textService } from '../../services/text'; import { RepositoryValidation } from '../../services/validation'; import { SectionLoading, RepositoryTypeLogo } from '../'; +import { useCore } from '../../app_context'; +import { getRepositoryTypeDocUrl } from '../../lib/type_to_doc_url'; interface Props { repository: Repository | EmptyRepository; @@ -54,6 +55,8 @@ export const RepositoryFormStepOne: React.FunctionComponent = ({ data: repositoryTypes = [], } = useLoadRepositoryTypes(); + const { docLinks } = useCore(); + const hasValidationErrors: boolean = !validation.isValid; const onTypeChange = (newType: RepositoryType) => { @@ -72,7 +75,7 @@ export const RepositoryFormStepOne: React.FunctionComponent = ({ }; const pluginDocLink = ( - + = ({ description={} /* EuiCard requires `description` */ footer={ = ({ values={{ docLink: ( = ({ saveError, onBack, }) => { + const { docLinks } = useCore(); const hasValidationErrors: boolean = !validation.isValid; const { name, @@ -76,7 +78,7 @@ export const RepositoryFormStepTwo: React.FunctionComponent = ({ diff --git a/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/data_streams_global_state_call_out.tsx b/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/data_streams_global_state_call_out.tsx index 6e3dc0a2270425..e99f122efaeebc 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/data_streams_global_state_call_out.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/data_streams_global_state_call_out.tsx @@ -9,8 +9,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { FunctionComponent } from 'react'; import { EuiCallOut, EuiLink } from '@elastic/eui'; - -import { documentationLinksService } from '../../../../services/documentation'; +import { useCore } from '../../../../app_context'; const i18nTexts = { callout: { @@ -20,13 +19,13 @@ const i18nTexts = { 'This snapshot contains {count, plural, one {a data stream} other {data streams}}', values: { count }, }), - body: () => ( + body: (docLink: string) => ( + {i18n.translate( 'xpack.snapshotRestore.restoreForm.dataStreamsWarningCallOut.body.learnMoreLink', { defaultMessage: 'Learn more' } @@ -44,6 +43,7 @@ interface Props { } export const DataStreamsGlobalStateCallOut: FunctionComponent = ({ dataStreamsCount }) => { + const { docLinks } = useCore(); return ( = ({ dataSt iconType="alert" color="warning" > - {i18nTexts.callout.body()} + {i18nTexts.callout.body(docLinks.links.snapshotRestore.createSnapshot)} ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx b/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx index e88bc7feef399f..bb66585579d7d1 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_logistics/step_logistics.tsx @@ -27,9 +27,7 @@ import { EuiSelectableOption } from '@elastic/eui'; import { csvToArray, isDataStreamBackingIndex } from '../../../../../../common/lib'; import { RestoreSettings } from '../../../../../../common/types'; -import { documentationLinksService } from '../../../../services/documentation'; - -import { useServices } from '../../../../app_context'; +import { useCore, useServices } from '../../../../app_context'; import { orderDataStreamsAndIndices } from '../../../lib'; import { DataStreamBadge } from '../../../data_stream_badge'; @@ -47,6 +45,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = errors, }) => { const { i18n } = useServices(); + const { docLinks } = useCore(); const { indices: unfilteredSnapshotIndices, dataStreams: snapshotDataStreams = [], @@ -166,7 +165,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = diff --git a/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_settings.tsx b/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_settings.tsx index 3f4789bceac595..1c27ee424ea31c 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_settings.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_settings.tsx @@ -24,8 +24,7 @@ import { } from '@elastic/eui'; import { RestoreSettings } from '../../../../../common/types'; import { REMOVE_INDEX_SETTINGS_SUGGESTIONS } from '../../../constants'; -import { documentationLinksService } from '../../../services/documentation'; -import { useServices } from '../../../app_context'; +import { useCore, useServices } from '../../../app_context'; import { StepProps } from './'; export const RestoreSnapshotStepSettings: React.FunctionComponent = ({ @@ -35,6 +34,7 @@ export const RestoreSnapshotStepSettings: React.FunctionComponent = ( errors, }) => { const { i18n } = useServices(); + const { docLinks } = useCore(); const { indexSettings, ignoreIndexSettings } = restoreSettings; const { dataStreams } = snapshotDetails; @@ -63,7 +63,7 @@ export const RestoreSnapshotStepSettings: React.FunctionComponent = ( // Index settings doc link const indexSettingsDocLink = ( - + = ( diff --git a/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx index 73e19eee8bf7a7..dcf087bb9ddc82 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx @@ -23,8 +23,7 @@ import { EuiCallOut, } from '@elastic/eui'; -import { useServices, useToastNotifications } from '../app_context'; -import { documentationLinksService } from '../services/documentation'; +import { useCore, useServices, useToastNotifications } from '../app_context'; import { Frequency, CronEditor } from '../../shared_imports'; import { DEFAULT_RETENTION_SCHEDULE, DEFAULT_RETENTION_FREQUENCY } from '../constants'; import { updateRetentionSchedule } from '../services/http'; @@ -44,6 +43,7 @@ export const RetentionSettingsUpdateModalProvider: React.FunctionComponent { const { i18n } = useServices(); + const { docLinks } = useCore(); const toastNotifications = useToastNotifications(); const [retentionSchedule, setRetentionSchedule] = useState(DEFAULT_RETENTION_SCHEDULE); @@ -185,7 +185,7 @@ export const RetentionSettingsUpdateModalProvider: React.FunctionComponent + { + switch (type) { + case REPOSITORY_TYPES.fs: + return docLinks.links.snapshotRestore.registerSharedFileSystem; + case REPOSITORY_TYPES.url: + return `${docLinks.links.snapshotRestore.registerUrl}`; + case REPOSITORY_TYPES.source: + return `${docLinks.links.snapshotRestore.registerSourceOnly}`; + case REPOSITORY_TYPES.s3: + return `${docLinks.links.plugins.s3Repo}`; + case REPOSITORY_TYPES.hdfs: + return `${docLinks.links.plugins.hdfsRepo}`; + case REPOSITORY_TYPES.azure: + return `${docLinks.links.plugins.azureRepo}`; + case REPOSITORY_TYPES.gcs: + return `${docLinks.links.plugins.gcsRepo}`; + default: + return `${docLinks.links.snapshotRestore.guide}`; + } +}; diff --git a/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts b/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts index e947dc8ee4ab63..2077e37227fb7f 100644 --- a/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts +++ b/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts @@ -13,7 +13,6 @@ import { ClientConfigType } from '../types'; import { httpService } from './services/http'; import { UiMetricService } from './services'; import { breadcrumbService, docTitleService } from './services/navigation'; -import { documentationLinksService } from './services/documentation'; import { AppDependencies } from './app_context'; import { renderApp } from '.'; @@ -28,13 +27,11 @@ export async function mountManagementSection( const { element, setBreadcrumbs, history } = params; const [core] = await coreSetup.getStartServices(); const { - docLinks, chrome: { docTitle }, } = core; docTitleService.setup(docTitle.change); breadcrumbService.setup(setBreadcrumbs); - documentationLinksService.setup(docLinks); const appDependencies: AppDependencies = { core, diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx index 130488d370c13d..e4a23bac636d8f 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx @@ -23,14 +23,13 @@ import { } from '@elastic/eui'; import { BASE_PATH, Section } from '../../constants'; -import { useConfig } from '../../app_context'; +import { useConfig, useCore } from '../../app_context'; import { breadcrumbService, docTitleService } from '../../services/navigation'; import { RepositoryList } from './repository_list'; import { SnapshotList } from './snapshot_list'; import { RestoreList } from './restore_list'; import { PolicyList } from './policy_list'; -import { documentationLinksService } from '../../services/documentation'; interface MatchParams { section: Section; @@ -43,6 +42,7 @@ export const SnapshotRestoreHome: React.FunctionComponent { const { slm_ui: slmUi } = useConfig(); + const { docLinks } = useCore(); const tabs: Array<{ id: Section; @@ -114,7 +114,7 @@ export const SnapshotRestoreHome: React.FunctionComponent = ({ onRepositoryDeleted, }) => { const { i18n, history } = useServices(); + const { docLinks } = useCore(); const { error, data: repositoryDetails } = useLoadRepository(repositoryName); const [verification, setVerification] = useState(undefined); const [cleanup, setCleanup] = useState(undefined); @@ -223,7 +224,7 @@ export const RepositoryDetails: React.FunctionComponent = ({ @@ -270,7 +270,7 @@ export const SnapshotList: React.FunctionComponent

    diff --git a/x-pack/plugins/snapshot_restore/public/application/services/documentation/documentation_links.ts b/x-pack/plugins/snapshot_restore/public/application/services/documentation/documentation_links.ts deleted file mode 100644 index 602a662d1ece23..00000000000000 --- a/x-pack/plugins/snapshot_restore/public/application/services/documentation/documentation_links.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { DocLinksStart } from '../../../../../../../src/core/public'; -import { REPOSITORY_TYPES } from '../../../../common/constants'; -import { RepositoryType } from '../../../../common/types'; -import { REPOSITORY_DOC_PATHS } from '../../constants'; - -class DocumentationLinksService { - private esDocBasePath: string = ''; - private esPluginDocBasePath: string = ''; - - public setup(docLinks: DocLinksStart): void { - const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docLinks; - const docsBase = `${ELASTIC_WEBSITE_URL}guide/en`; - - this.esDocBasePath = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}/`; - this.esPluginDocBasePath = `${docsBase}/elasticsearch/plugins/${DOC_LINK_VERSION}/`; - } - - public getRepositoryPluginDocUrl() { - return `${this.esPluginDocBasePath}${REPOSITORY_DOC_PATHS.plugins}`; - } - - public getRepositoryTypeDocUrl(type?: RepositoryType) { - switch (type) { - case REPOSITORY_TYPES.fs: - return `${this.esDocBasePath}${REPOSITORY_DOC_PATHS.fs}`; - case REPOSITORY_TYPES.url: - return `${this.esDocBasePath}${REPOSITORY_DOC_PATHS.url}`; - case REPOSITORY_TYPES.source: - return `${this.esDocBasePath}${REPOSITORY_DOC_PATHS.source}`; - case REPOSITORY_TYPES.s3: - return `${this.esPluginDocBasePath}${REPOSITORY_DOC_PATHS.s3}`; - case REPOSITORY_TYPES.hdfs: - return `${this.esPluginDocBasePath}${REPOSITORY_DOC_PATHS.hdfs}`; - case REPOSITORY_TYPES.azure: - return `${this.esPluginDocBasePath}${REPOSITORY_DOC_PATHS.azure}`; - case REPOSITORY_TYPES.gcs: - return `${this.esPluginDocBasePath}${REPOSITORY_DOC_PATHS.gcs}`; - default: - return `${this.esDocBasePath}${REPOSITORY_DOC_PATHS.default}`; - } - } - - public getSnapshotDocUrl() { - return `${this.esDocBasePath}snapshots-take-snapshot.html`; - } - - public getRestoreDocUrl() { - return `${this.esDocBasePath}snapshots-restore-snapshot.html`; - } - - public getRestoreIndexSettingsUrl() { - return `${this.esDocBasePath}snapshots-restore-snapshot.html#_changing_index_settings_during_restore`; - } - - public getIndexSettingsUrl() { - return `${this.esDocBasePath}index-modules.html`; - } - - public getDateMathIndexNamesUrl() { - return `${this.esDocBasePath}date-math-index-names.html`; - } - - public getSlmUrl() { - return `${this.esDocBasePath}slm-api-put.html`; - } - - public getCronUrl() { - return `${this.esDocBasePath}trigger-schedule.html#schedule-cron`; - } -} - -export const documentationLinksService = new DocumentationLinksService(); diff --git a/x-pack/plugins/snapshot_restore/public/application/services/documentation/index.ts b/x-pack/plugins/snapshot_restore/public/application/services/documentation/index.ts deleted file mode 100644 index 995c63513bda17..00000000000000 --- a/x-pack/plugins/snapshot_restore/public/application/services/documentation/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { documentationLinksService } from './documentation_links'; From 126ce49446b66b40f76c05aedfe01d02a740540f Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Fri, 19 Feb 2021 15:01:51 -0600 Subject: [PATCH 92/93] [docker] Default server.name to hostname (#90799) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/setup/docker.asciidoc | 1 - .../docker_generator/templates/kibana_yml.template.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index 75a9799d70fbdb..25883307e69f0d 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -120,7 +120,6 @@ The following settings have different default values when using the Docker images: [horizontal] -`server.name`:: `kibana` `server.host`:: `"0.0.0.0"` `elasticsearch.hosts`:: `http://elasticsearch:9200` `monitoring.ui.container.elasticsearch.enabled`:: `true` diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts b/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts index 86443061fca64c..c8eb16530507f9 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts @@ -17,7 +17,6 @@ function generator({ imageFlavor }: TemplateContext) { # # Default Kibana configuration for docker target - server.name: kibana server.host: "0.0.0.0" elasticsearch.hosts: [ "http://elasticsearch:9200" ] ${!imageFlavor ? 'monitoring.ui.container.elasticsearch.enabled: true' : ''} From f1db49ff70bb201670bc64b9e45f0729ef4ab071 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Fri, 19 Feb 2021 16:21:53 -0500 Subject: [PATCH 93/93] Skip flaky apm test #91673 (#92065) --- x-pack/test/apm_api_integration/tests/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index 3884b3ae750a5c..c030ffb347c860 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -15,7 +15,8 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte this.tags('ciGroup1'); loadTestFile(require.resolve('./alerts/chart_preview')); - loadTestFile(require.resolve('./correlations/slow_transactions')); + // Flaky, see https://github.com/elastic/kibana/issues/91673 + // loadTestFile(require.resolve('./correlations/slow_transactions')); loadTestFile(require.resolve('./csm/csm_services')); loadTestFile(require.resolve('./csm/has_rum_data'));