diff --git a/.backportrc.json b/.backportrc.json index e44d3ce114299..2752768194e0f 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -31,5 +31,7 @@ "^v8.0.0$": "master", "^v7.12.0$": "7.x", "^v(\\d+).(\\d+).\\d+$": "$1.$2" - } + }, + "autoMerge": true, + "autoMergeMethod": "squash" } diff --git a/.ci/Jenkinsfile_coverage b/.ci/Jenkinsfile_coverage index 1c1d21024ce91..0aa962a58f53c 100644 --- a/.ci/Jenkinsfile_coverage +++ b/.ci/Jenkinsfile_coverage @@ -3,14 +3,14 @@ library 'kibana-pipeline-library' kibanaLibrary.load() // load from the Jenkins instance -kibanaPipeline(timeoutMinutes: 240) { +kibanaPipeline(timeoutMinutes: 300) { catchErrors { def timestamp = new Date(currentBuild.startTimeInMillis).format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC")) withEnv([ "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/docs/api/actions-and-connectors.asciidoc b/docs/api/actions-and-connectors.asciidoc new file mode 100644 index 0000000000000..17e7ea1b7672a --- /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 0000000000000..af5ddd050e40e --- /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 0000000000000..e90b9ae44c5bd --- /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 0000000000000..12f1405eb4456 --- /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 0000000000000..6be554e65db04 --- /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 0000000000000..9863963c8395e --- /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 0000000000000..b800b7ff3b4f2 --- /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 0000000000000..e08ec2f8da1b6 --- /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/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 017e3ec57d340..54c065480b113 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 dc6804b0630bd..0bca16a0bb710 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/docs/user/alerting/action-types/email.asciidoc b/docs/user/alerting/action-types/email.asciidoc index d7a9373a6e2a9..3562be1e405f6 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 2c6da7c7c3026..2f459edea28f1 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 65e5ee4fc4a01..6e47d5618d598 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 aad192dbddb30..e1078a55ddd0d 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 b5ddb76d49b0c..112246ab91162 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 0acb92bcdb5ee..5d8782c14e581 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 a1fe7a2521b22..6a38e5c827ab2 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 6706dd2e5643f..e1ce91fc0c123 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 fff6814325ea4..2d626d53d1c77 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 20f1fc89367f2..2ae83bee1e06c 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[] diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index fb91f6a6a1c9a..50eb81279fc16 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -7,20 +7,19 @@ {kib} enables you to give shape to your data and navigate the Elastic Stack. With {kib}, you can: -* *Visualize and analyze your data.* -Search for hidden insights, visualize what you've found in charts, gauges, -maps and more, and combine them in a dashboard. - * *Search, observe, and protect.* From discovering documents to analyzing logs to finding security vulnerabilities, {kib} is your portal for accessing these capabilities and more. +* *Visualize and analyze your data.* +Search for hidden insights, visualize what you've found in charts, gauges, +maps and more, and combine them in a dashboard. + * *Manage, monitor, and secure the Elastic Stack.* Manage your indices and ingest pipelines, monitor the health of your Elastic Stack cluster, and control which users have access to which features. - *{kib} is for administrators, analysts, and business users.* As an admin, your role is to manage the Elastic Stack, from creating your deployment to getting {es} data into {kib}, and then @@ -37,103 +36,84 @@ 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] [float] -[[kibana-navigation-search]] -=== Search {kib} - -Using the Search field in the global header, you can -search for applications and objects, such as -dashboards and visualizations. - -Search suggestions include deep links into applications, -allowing you to directly navigate to the views you need most. - -[role="screenshot"] -image::images/app-navigation-search.png[Example of searching for apps] - -When searching for objects, you can search by type, name, and tag. -Tags are keywords or labels that you assign to {kib} objects, -so you can classify the objects in a way that is meaningful to you. -You can then quickly search for related objects based on shared tags. +[[extend-your-use-case]] +=== Search, observe, and protect -[role="screenshot"] -image::images/tags-search.png[Example of searching for tags] +Being able to search, observe, and protect your data is a requirement for any analyst. +{kib} provides solutions for each of these use cases. -To get the most from the search feature, follow these tips: +* https://www.elastic.co/guide/en/enterprise-search/current/index.html[*Enterprise Search*] enables you to create a search experience for your app, workplace, and website. -* Use the keyboard shortcut—Ctrl+/ on Windows and Linux, Command+/ on MacOS—to focus on the input at any time. +* {observability-guide}/observability-introduction.html[*Elastic Observability*] enables you to monitor and apply analytics in real time +to events happening across all your environments. You can analyze log events, monitor the performance metrics for the host or container +that it ran in, trace the transaction, and check the overall service availability. -* Use the provided syntax keywords. +* Designed for security analysts, {security-guide}/es-overview.html[*Elastic Security*] provides an overview of +the events and alerts from your environment. Elastic Security helps you defend +your organization from threats before damage and loss occur. + -[cols=2*] -|=== -|Search by type -|`type:dashboard` - -Available types: `application`, `canvas-workpad`, `dashboard`, `index-pattern`, `lens`, `maps`, `query`, `search`, `visualization` - -|Search by tag -|`tag:mytagname` + -`tag:"tag name with spaces"` - -|Search by type and name -|`type:dashboard my_dashboard_title` - -|Advanced searches -|`tag:(tagname1 or tagname2) my_dashboard_title` + -`type:lens tag:(tagname1 or tagname2)` + -`type:(dashboard or canvas-workpad) logs` + -|=== +[role="screenshot"] +image::siem/images/detections-ui.png[Detections view in Elastic Security] [float] [[visualize-and-analyze]] -=== Analyze your data +=== Visualize and analyze -Data analysis is the core functionality of {kib}. +Data analysis is a core functionality of {kib}. You can quickly search through large amounts of data, explore fields and values, and then use {kib}’s drag-and-drop interface to rapidly build charts, tables, metrics, and more. [role="screenshot"] -image::images/visualization-journey.png[User visualization journey] +image::images/visualization-journey.png[User data analysis journey] [[get-data-into-kibana]] -. *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. +[cols=2*] +|=== -. *Explore.* With <>, you can search your data for hidden +| *1* +| *Add data.* The best way to add {es} data to {kib} is to use one of our guided processes, +available from the <>. + +| *2* +| *Explore.* With <>, you can search your data for hidden insights and relationships. Ask your questions, and then filter the results to just the data you want. -You can also limit your results to the most recent documents added to {es}. +You can limit your results to the most recent documents added to {es}. -. *Visualize.* {kib} provides many options to create visualizations of your data, from +| *3* +| *Visualize.* {kib} provides many options to create visualizations of your data, from aggregation-based data to time series data. <> is your starting point to create visualizations, and then pulling them together to show your data from multiple perspectives. -. *Present.* With <>, you can display your data on a visually +| *4* +| *Present.* With <>, you can display your data on a visually compelling, pixel-perfect workpad. **Canvas** can give your data the “wow” factor needed to impress your CEO and captivate coworkers with a big-screen display. -. *Share.* Ready to <> your findings with a larger audience? {kib} offers many options—embed +| *5* +| *Share.* Ready to <> your findings with a larger audience? {kib} offers many options—embed a dashboard, share a link, export to PDF, and more. +|=== [float] ==== Plot location data on a map @@ -147,7 +127,7 @@ You can also visualize and track movement over space and through time. [float] ==== Model data behavior -To model the behavior of your data, you'll want to use +To model the behavior of your data, you'll use <>. This app can help you extract insights from your data that you might otherwise miss. You can forecast unusual behavior in your time series data. @@ -164,37 +144,17 @@ can help you uncover website vulnerabilities that hackers are targeting, so you can harden your website. Or, you might provide graph-based personalized recommendations to your e-commerce customers. -[float] -[[extend-your-use-case]] -=== Search, observe, and protect - -Being able to search, observe, and protect your data is a requirement for any analyst. -{kib} provides solutions for each of these use cases. - -* https://www.elastic.co/guide/en/enterprise-search/current/index.html[*Enterprise Search*] enables you to create a search experience for your app, workplace, and website. - -* {observability-guide}/observability-introduction.html[*Elastic Observability*] enables you to monitor and apply analytics in real time -to events happening across all your environments. You can analyze log events, monitor the performance metrics for the host or container -that it ran in, trace the transaction, and check the overall service availability. - -* Designed for security analysts, {security-guide}/es-overview.html[*Elastic Security*] provides an overview of -the events and alerts from your environment. Elastic Security helps you defend -your organization from threats before damage and loss occur. -+ -[role="screenshot"] -image::siem/images/detections-ui.png[] - [float] [[manage-all-things-stack]] === Manage all things Elastic Stack -{kib}'s <> takes you under the hood, -so you can twist the levers and turn the knobs. *Stack Management* provides +{kib}'s <> UIs takes you under the hood, +so you can twist the levers and turn the knobs. You'll find guided processes for administering all things Elastic Stack, including data, indices, clusters, alerts, and security. [role="screenshot"] -image::images/intro-management.png[] +image::images/intro-management.png[Index Management view in Stack Management] [float] ==== Manage your data, indices, and clusters @@ -216,8 +176,8 @@ that exists in almost every use case. For example, you might set an alert to not * System resources, such as memory, CPU and disk space, take a dip. * An unusually high number of service requests, suspicious processes, and login attempts occurs. -An alert is triggered when a specified condition is met. For example, -an alert might trigger when the average or max of one of +An alert triggers when a specified condition is met. For example, +you can trigger an alert when the average or max of one of your metrics exceeds a threshold within a specified time frame. When the alert triggers, you can send a notification to a system that is part of @@ -240,7 +200,7 @@ Think of a space as its own mini {kib} installation—it’s isolated from a so you can tailor it to your specific needs without impacting others. [role="screenshot"] -image::images/select-your-space.png[Space selector screen] +image::images/select-your-space.png[Space selector view] Most of {kib}’s entities are space-aware, including dashboards, visualizations, index patterns, Canvas workpads, Timelion visualizations, graphs, tags, and machine learning jobs. @@ -271,7 +231,7 @@ to specific features on a per-user basis, you must configure <>. [role="screenshot"] -image::images/features-control.png[Features Controls screen] +image::images/features-control.png[Features Controls view] [float] [[intro-kibana-Security]] @@ -289,7 +249,7 @@ Kibana supports several <>, allowing you to login using {es}’s built-in realms, or by your own single sign-on provider. [role="screenshot"] -image::images/login-screen.png[Login screen] +image::images/login-screen.png[Login page] [float] ==== Secure access @@ -320,6 +280,52 @@ record of who did what, when. The {kib} audit log will record this information f which can then be correlated with {es} audit logs to gain more insights into your users’ behavior. For more information, see <>. +[float] +[[kibana-navigation-search]] +=== Quickly find apps and objects + +Using the search field in the global header, you can +search for applications and objects, such as +dashboards and visualizations. Search suggestions include deep links into applications, +allowing you to directly navigate to the views you need most. + +[role="screenshot"] +image::images/app-navigation-search.png[Example of searching for apps] + +When searching for objects, you can search by type, name, and tag. +Tags are keywords or labels that you assign to {kib} objects, +so you can classify the objects in a way that is meaningful to you. +You can then quickly search for related objects based on shared tags. + +[role="screenshot"] +image::images/tags-search.png[Example of searching for tags] + +To get the most from the search feature, follow these tips: + +* Use the keyboard shortcut—Ctrl+/ on Windows and Linux, Command+/ on MacOS—to focus on the input at any time. + +* Use the provided syntax keywords. ++ +[cols=2*] +|=== +|Search by type +|`type:dashboard` + +Available types: `application`, `canvas-workpad`, `dashboard`, `index-pattern`, `lens`, `maps`, `query`, `search`, `visualization` + +|Search by tag +|`tag:mytagname` + +`tag:"tag name with spaces"` + +|Search by type and name +|`type:dashboard my_dashboard_title` + +|Advanced searches +|`tag:(tagname1 or tagname2) my_dashboard_title` + +`type:lens tag:(tagname1 or tagname2)` + +`type:(dashboard or canvas-workpad) logs` + +|=== + [float] [[whats-the-right-app]] === What’s the right app for you? @@ -345,32 +351,6 @@ the <>. |See the full list of {kib} features |The https://www.elastic.co/kibana/features[{kib} features page on elastic.co] -2+| *Analyze and visualize your data* - -|Know what’s in your data -|<> - -|Create charts and other visualizations -|<> - -|Show your data from different perspectives -|<> - -|Work with location data -|<> - -|Create a presentation of your data -|<> - -|Generate models for your data’s behavior -|<> - -|Explore connections in your data -|<> - -|Share your data -|<>, <> - 2+|*Build a search experience* |Create a search experience for your workplace @@ -414,6 +394,32 @@ the <>. |View and manage hosts that are running Endpoint Security |{security-guide}/admin-page-ov.html[Administration] +2+| *Analyze and visualize your data* + +|Know what’s in your data +|<> + +|Create charts and other visualizations +|<> + +|Show your data from different perspectives +|<> + +|Work with location data +|<> + +|Create a presentation of your data +|<> + +|Generate models for your data’s behavior +|<> + +|Explore connections in your data +|<> + +|Share your data +|<>, <> + 2+|*Administer your Kibana instance* |Manage your Elasticsearch data @@ -435,7 +441,7 @@ the <>. [float] [[try-kibana]] -=== Getting help +=== How to get help Using our in-product guidance can help you get up and running, faster. Click the help icon image:images/intro-help-icon.png[Help icon in navigation bar] diff --git a/package.json b/package.json index 0381c42f021b1..d03dfe3489c52 100644 --- a/package.json +++ b/package.json @@ -328,7 +328,6 @@ "wellknown": "^0.5.0", "whatwg-fetch": "^3.0.0", "xml2js": "^0.4.22", - "xregexp": "4.2.4", "yauzl": "^2.10.0" }, "devDependencies": { @@ -353,7 +352,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", @@ -594,7 +593,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/renovate.json5 b/renovate.json5 index 415aa71fc3820..52d7a06c88339 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -11,22 +11,10 @@ ], baseBranches: [ 'master', + '7.x', ], - labels: [ - 'release_note:skip', - 'renovate', - 'v8.0.0', - 'v7.11.0', - ], - major: { - labels: [ - 'release_note:skip', - 'renovate', - 'v8.0.0', - 'v7.11.0', - 'renovate:major', - ], - }, + prConcurrentLimit: 0, + prHourlyLimit: 0, separateMajorMinor: false, masterIssue: true, rangeStrategy: 'bump', @@ -49,12 +37,31 @@ groupName: '@elastic/charts', packageNames: ['@elastic/charts'], reviewers: ['markov00'], + matchBaseBranches: ['master'], + labels: ['release_note:skip', 'v8.0.0', 'v7.12.0'], + enabled: true, + }, + { + groupName: '@elastic/elasticsearch', + packageNames: ['@elastic/elasticsearch'], + reviewers: ['team:kibana-operations'], + matchBaseBranches: ['master'], + labels: ['release_note:skip', 'v8.0.0', 'Team:Operations', 'backport:skip'], + enabled: true, + }, + { + groupName: '@elastic/elasticsearch', + packageNames: ['@elastic/elasticsearch'], + reviewers: ['team:kibana-operations'], + matchBaseBranches: ['7.x'], + labels: ['release_note:skip', 'v7.12.0', 'Team:Operations', 'backport:skip'], enabled: true, }, { groupName: 'vega related modules', packageNames: ['vega', 'vega-lite', 'vega-schema-url-parser', 'vega-tooltip'], reviewers: ['team:kibana-app'], + matchBaseBranches: ['master'], labels: ['Feature:Vega', 'Team:KibanaApp'], enabled: true, }, diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 0d40899544c08..937a89e12b755 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 2e23b26f636c8..e29173d1495af 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/src/plugins/apm_oss/server/tutorial/index_pattern.json b/src/plugins/apm_oss/server/tutorial/index_pattern.json index 93a2393b70fa4..b0a1ac1ccd379 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" }, 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 e875967616bbd..0c303b92bf1a1 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/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 9e889e85734ee..23ad7af14b093 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 @@ -345,8 +345,13 @@ describe('SearchSource', () => { }); test('allows you to override computed fields if you provide a format', async () => { + const indexPatternFields = indexPattern.fields; + indexPatternFields.getByType = (type) => { + return []; + }; searchSource.setField('index', ({ ...indexPattern, + fields: indexPatternFields, getComputedFields: () => ({ storedFields: [], scriptFields: {}, @@ -379,6 +384,11 @@ describe('SearchSource', () => { test('injects a date format for computed docvalue fields while merging other properties', async () => { searchSource.setField('index', ({ ...indexPattern, + fields: { + getByType: () => { + return []; + }, + }, getComputedFields: () => ({ storedFields: [], scriptFields: {}, @@ -625,7 +635,7 @@ describe('SearchSource', () => { searchSource.setField('fields', ['hello', '@timestamp', 'foo-a', 'bar']); const request = await searchSource.getSearchRequestBody(); - expect(request.fields).toEqual(['hello', '@timestamp', 'bar']); + expect(request.fields).toEqual(['hello', '@timestamp', 'bar', 'date']); expect(request.script_fields).toEqual({ hello: {} }); expect(request.stored_fields).toEqual(['@timestamp', 'bar']); }); @@ -681,6 +691,60 @@ describe('SearchSource', () => { }); }); + describe('handling date fields', () => { + test('adds date format to any date field', async () => { + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: [{ field: '@timestamp' }], + }), + fields: { + getByType: () => [{ name: '@timestamp', esTypes: ['date_nanos'] }], + }, + getSourceFiltering: () => ({ excludes: [] }), + } as unknown) as IndexPattern); + searchSource.setField('fields', ['*']); + + const request = await searchSource.getSearchRequestBody(); + expect(request.fields).toEqual([ + '*', + { field: '@timestamp', format: 'strict_date_optional_time_nanos' }, + ]); + }); + + test('adds date format to any date field except the one excluded by source filters', async () => { + const indexPatternFields = indexPattern.fields; + // @ts-ignore + indexPatternFields.getByType = (type) => { + return [ + { name: '@timestamp', esTypes: ['date_nanos'] }, + { name: 'custom_date', esTypes: ['date'] }, + ]; + }; + searchSource.setField('index', ({ + ...indexPattern, + getComputedFields: () => ({ + storedFields: [], + scriptFields: {}, + docvalueFields: [{ field: '@timestamp' }, { field: 'custom_date' }], + }), + fields: indexPatternFields, + getSourceFiltering: () => ({ excludes: ['custom_date'] }), + } as unknown) as IndexPattern); + searchSource.setField('fields', ['*']); + + const request = await searchSource.getSearchRequestBody(); + expect(request.fields).toEqual([ + { field: 'foo-bar' }, + { field: 'field1' }, + { field: 'field2' }, + { field: '@timestamp', format: 'strict_date_optional_time_nanos' }, + ]); + }); + }); + describe(`#setField('index')`, () => { describe('auto-sourceFiltering', () => { describe('new index pattern assigned', () => { 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 2cf0455ae2df8..8406c4900bef7 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 } from 'lodash'; +import { uniqueId, keyBy, pick, difference, omit, isFunction, isEqual, uniqWith } from 'lodash'; import { map, switchMap, tap } from 'rxjs/operators'; import { defer, from } from 'rxjs'; import { isObject } from 'rxjs/internal-compatibility'; @@ -548,6 +548,37 @@ export class SearchSource { })); } + private getFieldFromDocValueFieldsOrIndexPattern( + docvaluesIndex: Record, + fld: SearchFieldValue, + index?: IndexPattern + ) { + if (typeof fld === 'string') { + return fld; + } + const fieldName = this.getFieldName(fld); + const field = { + ...docvaluesIndex[fieldName], + ...fld, + }; + if (!index) { + return field; + } + const { fields } = index; + const dateFields = fields.getByType('date'); + const dateField = dateFields.find((indexPatternField) => indexPatternField.name === fieldName); + if (!dateField) { + return field; + } + const { esTypes } = dateField; + if (esTypes?.includes('date_nanos')) { + field.format = 'strict_date_optional_time_nanos'; + } else if (esTypes?.includes('date')) { + field.format = 'strict_date_optional_time'; + } + return field; + } + private flatten() { const { getConfig } = this.dependencies; const searchRequest = this.mergeProps(); @@ -657,22 +688,25 @@ export class SearchSource { // if items that are in the docvalueFields are provided, we should // inject the format from the computed fields if one isn't given const docvaluesIndex = keyBy(filteredDocvalueFields, 'field'); - body.fields = this.getFieldsWithoutSourceFilters(index, body.fields).map( - (fld: SearchFieldValue) => { - const fieldName = this.getFieldName(fld); - if (Object.keys(docvaluesIndex).includes(fieldName)) { - // either provide the field object from computed docvalues, - // or merge the user-provided field with the one in docvalues - return typeof fld === 'string' - ? docvaluesIndex[fld] - : { - ...docvaluesIndex[fieldName], - ...fld, - }; - } - return fld; + const bodyFields = this.getFieldsWithoutSourceFilters(index, body.fields); + body.fields = uniqWith( + bodyFields.concat(filteredDocvalueFields), + (fld1: SearchFieldValue, fld2: SearchFieldValue) => { + const field1Name = this.getFieldName(fld1); + const field2Name = this.getFieldName(fld2); + return field1Name === field2Name; } - ); + ).map((fld: SearchFieldValue) => { + const fieldName = this.getFieldName(fld); + if (Object.keys(docvaluesIndex).includes(fieldName)) { + // either provide the field object from computed docvalues, + // or merge the user-provided field with the one in docvalues + return typeof fld === 'string' + ? docvaluesIndex[fld] + : this.getFieldFromDocValueFieldsOrIndexPattern(docvaluesIndex, fld, index); + } + return fld; + }); } } else { body.fields = filteredDocvalueFields; diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 3733e86698958..c2bbbf2e57a9a 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -169,10 +169,10 @@ function discoverController($route, $scope, Promise) { let inspectorRequest; let isChangingIndexPattern = false; const savedSearch = $route.current.locals.savedObjects.savedSearch; - $scope.searchSource = savedSearch.searchSource; + const persistentSearchSource = savedSearch.searchSource; $scope.indexPattern = resolveIndexPattern( $route.current.locals.savedObjects.ip, - $scope.searchSource, + persistentSearchSource, toastNotifications ); $scope.useNewFieldsApi = !config.get(SEARCH_FIELDS_FROM_SOURCE); @@ -370,25 +370,19 @@ function discoverController($route, $scope, Promise) { }); }; - $scope.searchSource - .setField('index', $scope.indexPattern) - .setField('highlightAll', true) - .setField('version', true); - - // Even when searching rollups, we want to use the default strategy so that we get back a - // document-like response. - $scope.searchSource.setPreferredSearchStrategyId('default'); + persistentSearchSource.setField('index', $scope.indexPattern); // searchSource which applies time range - const timeRangeSearchSource = savedSearch.searchSource.create(); + const volatileSearchSource = savedSearch.searchSource.create(); if (isDefaultType($scope.indexPattern)) { - timeRangeSearchSource.setField('filter', () => { + volatileSearchSource.setField('filter', () => { return timefilter.createFilter($scope.indexPattern); }); } - $scope.searchSource.setParent(timeRangeSearchSource); + volatileSearchSource.setParent(persistentSearchSource); + $scope.volatileSearchSource = volatileSearchSource; const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; chrome.docTitle.change(`Discover${pageTitleSuffix}`); @@ -403,7 +397,8 @@ function discoverController($route, $scope, Promise) { } function getStateDefaults() { - const query = $scope.searchSource.getField('query') || data.query.queryString.getDefaultQuery(); + const query = + persistentSearchSource.getField('query') || data.query.queryString.getDefaultQuery(); const sort = getSortArray(savedSearch.sort, $scope.indexPattern); const columns = getDefaultColumns(); @@ -415,7 +410,7 @@ function discoverController($route, $scope, Promise) { columns, index: $scope.indexPattern.id, interval: 'auto', - filters: _.cloneDeep($scope.searchSource.getOwnField('filter')), + filters: _.cloneDeep(persistentSearchSource.getOwnField('filter')), }; if (savedSearch.grid) { defaultState.grid = savedSearch.grid; @@ -556,7 +551,7 @@ function discoverController($route, $scope, Promise) { .then(function () { $scope.fetchStatus = fetchStatuses.LOADING; logInspectorRequest({ searchSessionId }); - return $scope.searchSource.fetch({ + return $scope.volatileSearchSource.fetch({ abortSignal: abortController.signal, sessionId: searchSessionId, }); @@ -603,11 +598,13 @@ function discoverController($route, $scope, Promise) { } function onResults(resp) { - inspectorRequest.stats(getResponseInspectorStats(resp, $scope.searchSource)).ok({ json: resp }); + inspectorRequest + .stats(getResponseInspectorStats(resp, $scope.volatileSearchSource)) + .ok({ json: resp }); if (getTimeField() && !$scope.state.hideChart) { const tabifiedData = tabifyAggResponse($scope.opts.chartAggConfigs, resp); - $scope.searchSource.rawResponse = resp; + $scope.volatileSearchSource.rawResponse = resp; $scope.histogramData = discoverResponseHandler( tabifiedData, getDimensions($scope.opts.chartAggConfigs.aggs, $scope.timeRange) @@ -635,8 +632,8 @@ function discoverController($route, $scope, Promise) { defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', }); inspectorRequest = inspectorAdapters.requests.start(title, { description, searchSessionId }); - inspectorRequest.stats(getRequestInspectorStats($scope.searchSource)); - $scope.searchSource.getSearchRequestBody().then((body) => { + inspectorRequest.stats(getRequestInspectorStats($scope.volatileSearchSource)); + $scope.volatileSearchSource.getSearchRequestBody().then((body) => { inspectorRequest.json(body); }); } @@ -693,9 +690,11 @@ function discoverController($route, $scope, Promise) { }; $scope.updateDataSource = () => { - const { indexPattern, searchSource, useNewFieldsApi } = $scope; + const { indexPattern, useNewFieldsApi } = $scope; const { columns, sort } = $scope.state; - updateSearchSource(searchSource, { + updateSearchSource({ + persistentSearchSource, + volatileSearchSource: $scope.volatileSearchSource, indexPattern, services, sort, @@ -731,12 +730,12 @@ function discoverController($route, $scope, Promise) { visStateAggs ); - $scope.searchSource.onRequestStart((searchSource, options) => { + $scope.volatileSearchSource.onRequestStart((searchSource, options) => { if (!$scope.opts.chartAggConfigs) return; return $scope.opts.chartAggConfigs.onSearchRequestStart(searchSource, options); }); - $scope.searchSource.setField('aggs', function () { + $scope.volatileSearchSource.setField('aggs', function () { if (!$scope.opts.chartAggConfigs) return; return $scope.opts.chartAggConfigs.toDsl(); }); diff --git a/src/plugins/discover/public/application/angular/discover_datagrid.html b/src/plugins/discover/public/application/angular/discover_datagrid.html index e59ebbb0fafd0..42218568a838d 100644 --- a/src/plugins/discover/public/application/angular/discover_datagrid.html +++ b/src/plugins/discover/public/application/angular/discover_datagrid.html @@ -17,7 +17,7 @@ reset-query="resetQuery" result-state="resultState" rows="rows" - search-source="searchSource" + search-source="volatileSearchSource" set-index-pattern="setIndexPattern" show-save-query="showSaveQuery" state="state" diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index 501496494106a..a01f285b1a150 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -13,7 +13,7 @@ reset-query="resetQuery" result-state="resultState" rows="rows" - search-source="searchSource" + search-source="volatileSearchSource" state="state" time-range="timeRange" top-nav-menu="topNavMenu" diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx index 71650a4a38472..1d183aa75cf3a 100644 --- a/src/plugins/discover/public/application/components/discover.tsx +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -206,6 +206,7 @@ export function Discover({ query={state.query} savedQuery={state.savedQuery} updateQuery={updateQuery} + searchSource={searchSource} />

diff --git a/src/plugins/discover/public/application/components/discover_grid/constants.ts b/src/plugins/discover/public/application/components/discover_grid/constants.ts index 03e5740793396..015d0b65246f2 100644 --- a/src/plugins/discover/public/application/components/discover_grid/constants.ts +++ b/src/plugins/discover/public/application/components/discover_grid/constants.ts @@ -8,7 +8,6 @@ // data types export const kibanaJSON = 'kibana-json'; -export const geoPoint = 'geo-point'; export const gridStyle = { border: 'all', fontSize: 's', diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx index 2317b8841a37a..1a721a400803e 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx @@ -27,7 +27,7 @@ describe('Discover grid columns ', function () { "cellActions": undefined, "display": undefined, "id": "extension", - "isSortable": undefined, + "isSortable": false, "schema": "kibana-json", }, Object { @@ -42,7 +42,7 @@ describe('Discover grid columns ', function () { "cellActions": undefined, "display": undefined, "id": "message", - "isSortable": undefined, + "isSortable": false, "schema": "kibana-json", }, ] @@ -67,7 +67,7 @@ describe('Discover grid columns ', function () { "cellActions": undefined, "display": undefined, "id": "extension", - "isSortable": undefined, + "isSortable": false, "schema": "kibana-json", }, Object { @@ -79,7 +79,7 @@ describe('Discover grid columns ', function () { "cellActions": undefined, "display": undefined, "id": "message", - "isSortable": undefined, + "isSortable": false, "schema": "kibana-json", }, ] @@ -105,7 +105,7 @@ describe('Discover grid columns ', function () { "display": "Time (timestamp)", "id": "timestamp", "initialWidth": 180, - "isSortable": undefined, + "isSortable": false, "schema": "kibana-json", }, Object { @@ -120,7 +120,7 @@ describe('Discover grid columns ', function () { "cellActions": undefined, "display": undefined, "id": "extension", - "isSortable": undefined, + "isSortable": false, "schema": "kibana-json", }, Object { @@ -135,7 +135,7 @@ describe('Discover grid columns ', function () { "cellActions": undefined, "display": undefined, "id": "message", - "isSortable": undefined, + "isSortable": false, "schema": "kibana-json", }, ] diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx index 2e9bd33c60659..c245b402137a0 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx @@ -47,7 +47,7 @@ export function buildEuiGridColumn( const column: EuiDataGridColumn = { id: columnName, schema: getSchemaByKbnType(indexPatternField?.type), - isSortable: indexPatternField?.sortable, + isSortable: indexPatternField?.sortable === true, display: columnName === '_source' ? i18n.translate('discover.grid.documentHeader', { diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_schema.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_schema.tsx index 83ade88386dbc..ca5b2c9f19918 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_schema.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_schema.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiCodeBlock, EuiDataGridPopoverContents } from '@elastic/eui'; -import { geoPoint, kibanaJSON } from './constants'; +import { kibanaJSON } from './constants'; import { KBN_FIELD_TYPES } from '../../../../../data/common'; export function getSchemaByKbnType(kbnType: string | undefined) { @@ -24,8 +24,6 @@ export function getSchemaByKbnType(kbnType: string | undefined) { return 'string'; case KBN_FIELD_TYPES.DATE: return 'datetime'; - case KBN_FIELD_TYPES.GEO_POINT: - return geoPoint; default: return kibanaJSON; } @@ -43,15 +41,6 @@ export function getSchemaDetectors() { icon: '', color: '', }, - { - type: geoPoint, - detector() { - return 0; // this schema is always explicitly defined - }, - sortTextAsc: '', - sortTextDesc: '', - icon: 'tokenGeo', - }, ]; } @@ -60,9 +49,6 @@ export function getSchemaDetectors() { */ export function getPopoverContents(): EuiDataGridPopoverContents { return { - [geoPoint]: ({ children }) => { - return {children}; - }, [kibanaJSON]: ({ children }) => { return ( diff --git a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx index 45f30a9d26f93..cfcdbec475eda 100644 --- a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx @@ -7,7 +7,6 @@ */ import React, { Fragment, useContext, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; import themeLight from '@elastic/eui/dist/eui_theme_light.json'; import themeDark from '@elastic/eui/dist/eui_theme_dark.json'; @@ -83,21 +82,6 @@ export const getRenderCellValueFn = ( return {JSON.stringify(rowFlattened[columnId])}; } - if (field?.type === 'geo_point' && rowFlattened && rowFlattened[columnId]) { - const valueFormatted = rowFlattened[columnId] as { lat: number; lon: number }; - return ( -
- {i18n.translate('discover.latitudeAndLongitude', { - defaultMessage: 'Lat: {lat} Lon: {lon}', - values: { - lat: valueFormatted?.lat, - lon: valueFormatted?.lon, - }, - })} -
- ); - } - const valueFormatted = indexPattern.formatField(row, columnId); if (typeof valueFormatted === 'undefined') { return -; diff --git a/src/plugins/discover/public/application/components/discover_topnav.test.tsx b/src/plugins/discover/public/application/components/discover_topnav.test.tsx index 50c9bcfb5d255..891dc63c92c7c 100644 --- a/src/plugins/discover/public/application/components/discover_topnav.test.tsx +++ b/src/plugins/discover/public/application/components/discover_topnav.test.tsx @@ -20,7 +20,7 @@ import { SavedObject } from '../../../../../core/types'; import { DiscoverTopNav, DiscoverTopNavProps } from './discover_topnav'; import { RequestAdapter } from '../../../../inspector/common/adapters/request'; import { TopNavMenu } from '../../../../navigation/public'; -import { Query } from '../../../../data/common'; +import { ISearchSource, Query } from '../../../../data/common'; import { DiscoverSearchSessionManager } from '../angular/discover_search_session'; import { Subject } from 'rxjs'; @@ -61,6 +61,7 @@ function getProps(): DiscoverTopNavProps { savedQuery: '', updateQuery: jest.fn(), onOpenInspector: jest.fn(), + searchSource: {} as ISearchSource, }; } diff --git a/src/plugins/discover/public/application/components/discover_topnav.tsx b/src/plugins/discover/public/application/components/discover_topnav.tsx index fd2aba22aa41d..ee59ee13583bd 100644 --- a/src/plugins/discover/public/application/components/discover_topnav.tsx +++ b/src/plugins/discover/public/application/components/discover_topnav.tsx @@ -10,7 +10,7 @@ import { DiscoverProps } from './types'; import { getTopNavLinks } from './top_nav/get_top_nav_links'; import { Query, TimeRange } from '../../../../data/common/query'; -export type DiscoverTopNavProps = Pick & { +export type DiscoverTopNavProps = Pick & { onOpenInspector: () => void; query?: Query; savedQuery?: string; @@ -24,6 +24,7 @@ export const DiscoverTopNav = ({ query, savedQuery, updateQuery, + searchSource, }: DiscoverTopNavProps) => { const showDatePicker = useMemo(() => indexPattern.isTimeBased(), [indexPattern]); const { TopNavMenu } = opts.services.navigation.ui; @@ -38,8 +39,9 @@ export const DiscoverTopNav = ({ services: opts.services, state: opts.stateContainer, onOpenInspector, + searchSource, }), - [indexPattern, opts, onOpenInspector] + [indexPattern, opts, onOpenInspector, searchSource] ); const updateSavedQueryId = (newSavedQueryId: string | undefined) => { diff --git a/src/plugins/discover/public/application/components/doc/doc.test.tsx b/src/plugins/discover/public/application/components/doc/doc.test.tsx index 623d30d5f53ec..deaaa1853ae9d 100644 --- a/src/plugins/discover/public/application/components/doc/doc.test.tsx +++ b/src/plugins/discover/public/application/components/doc/doc.test.tsx @@ -13,6 +13,7 @@ import { mountWithIntl } from '@kbn/test/jest'; import { ReactWrapper } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; import { Doc, DocProps } from './doc'; +import { SEARCH_FIELDS_FROM_SOURCE as mockSearchFieldsFromSource } from '../../../../common'; const mockSearchApi = jest.fn(); @@ -36,6 +37,13 @@ jest.mock('../../../kibana_services', () => { }, }, }, + uiSettings: { + get: (key: string) => { + if (key === mockSearchFieldsFromSource) { + return false; + } + }, + }, }), getDocViewsRegistry: () => ({ addDocView(view: any) { 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 6cb0bf1288d3a..ef2619070a6d8 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 @@ -10,6 +10,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { buildSearchBody, useEsDocSearch, ElasticRequestState } from './use_es_doc_search'; import { DocProps } from './doc'; import { Observable } from 'rxjs'; +import { SEARCH_FIELDS_FROM_SOURCE as mockSearchFieldsFromSource } from '../../../../common'; const mockSearchResult = new Observable(); @@ -22,19 +23,52 @@ jest.mock('../../../kibana_services', () => ({ }), }, }, + uiSettings: { + get: (key: string) => { + if (key === mockSearchFieldsFromSource) { + return false; + } + }, + }, }), })); describe('Test of helper / hook', () => { - test('buildSearchBody', () => { + test('buildSearchBody given useNewFieldsApi is false', () => { const indexPattern = { getComputedFields: () => ({ storedFields: [], scriptFields: [], docvalueFields: [] }), } as any; - const actual = buildSearchBody('1', indexPattern); + const actual = buildSearchBody('1', indexPattern, false); expect(actual).toMatchInlineSnapshot(` Object { "_source": true, "docvalue_fields": Array [], + "fields": undefined, + "query": Object { + "ids": Object { + "values": Array [ + "1", + ], + }, + }, + "script_fields": Array [], + "stored_fields": Array [], + } + `); + }); + + test('buildSearchBody useNewFieldsApi is true', () => { + const indexPattern = { + getComputedFields: () => ({ storedFields: [], scriptFields: [], docvalueFields: [] }), + } as any; + const actual = buildSearchBody('1', indexPattern, true); + expect(actual).toMatchInlineSnapshot(` + Object { + "_source": false, + "docvalue_fields": Array [], + "fields": Array [ + "*", + ], "query": Object { "ids": Object { "values": Array [ 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 2a63a62650ca9..295b2ab383119 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 @@ -6,10 +6,11 @@ * Side Public License, v 1. */ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useMemo } from 'react'; import { IndexPattern, getServices } from '../../../kibana_services'; import { DocProps } from './doc'; import { ElasticSearchHit } from '../../doc_views/doc_views_types'; +import { SEARCH_FIELDS_FROM_SOURCE } from '../../../../common'; export enum ElasticRequestState { Loading, @@ -23,7 +24,11 @@ export enum ElasticRequestState { * helper function to build a query body for Elasticsearch * https://www.elastic.co/guide/en/elasticsearch/reference/current//query-dsl-ids-query.html */ -export function buildSearchBody(id: string, indexPattern: IndexPattern): Record { +export function buildSearchBody( + id: string, + indexPattern: IndexPattern, + useNewFieldsApi: boolean +): Record { const computedFields = indexPattern.getComputedFields(); return { @@ -33,7 +38,8 @@ export function buildSearchBody(id: string, indexPattern: IndexPattern): Record< }, }, stored_fields: computedFields.storedFields, - _source: true, + _source: !useNewFieldsApi, + fields: useNewFieldsApi ? ['*'] : undefined, script_fields: computedFields.scriptFields, docvalue_fields: computedFields.docvalueFields, }; @@ -51,6 +57,8 @@ export function useEsDocSearch({ const [indexPattern, setIndexPattern] = useState(null); const [status, setStatus] = useState(ElasticRequestState.Loading); const [hit, setHit] = useState(null); + const { data, uiSettings } = useMemo(() => getServices(), []); + const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]); useEffect(() => { async function requestData() { @@ -58,11 +66,11 @@ export function useEsDocSearch({ const indexPatternEntity = await indexPatternService.get(indexPatternId); setIndexPattern(indexPatternEntity); - const { rawResponse } = await getServices() - .data.search.search({ + const { rawResponse } = await data.search + .search({ params: { index, - body: buildSearchBody(id, indexPatternEntity), + body: buildSearchBody(id, indexPatternEntity, useNewFieldsApi), }, }) .toPromise(); @@ -86,6 +94,6 @@ export function useEsDocSearch({ } } requestData(); - }, [id, index, indexPatternId, indexPatternService]); + }, [id, index, indexPatternId, indexPatternService, data.search, useNewFieldsApi]); return [status, hit, indexPattern]; } 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 62e19b83b016e..6afa7f89371f9 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 571af94f29372..b9b068ce4bd07 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 1da75b4523910..25454a3bad38a 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 d6f48a9b3c774..0000000000000 --- 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 7dab2b199b018..0000000000000 --- 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 0000000000000..4f27158eee04f --- /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 0000000000000..5521df5b363ac --- /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 dd56a1077f1ac..4ccb3010d5a2b 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 0000000000000..85d6aad755250 --- /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/application/components/sidebar/lib/group_fields.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts index 89980f7fd0f54..9792e98ba84c7 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 c7242a8518b55..eefb96b78aac6 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); } } diff --git a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts index 89cb60700074d..30edb102c420a 100644 --- a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts +++ b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { ISearchSource } from 'src/plugins/data/public'; import { getTopNavLinks } from './get_top_nav_links'; import { inspectorPluginMock } from '../../../../../inspector/public/mocks'; import { indexPatternMock } from '../../../__mocks__/index_pattern'; @@ -33,6 +34,7 @@ test('getTopNavLinks result', () => { savedSearch: savedSearchMock, services, state, + searchSource: {} as ISearchSource, }); expect(topNavLinks).toMatchInlineSnapshot(` Array [ diff --git a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts index 513508c478aa9..a1215836f9c5f 100644 --- a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts +++ b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts @@ -15,7 +15,7 @@ import { Adapters } from '../../../../../inspector/common/adapters'; import { SavedSearch } from '../../../saved_searches'; import { onSaveSearch } from './on_save_search'; import { GetStateReturn } from '../../angular/discover_state'; -import { IndexPattern } from '../../../kibana_services'; +import { IndexPattern, ISearchSource } from '../../../kibana_services'; /** * Helper function to build the top nav links @@ -29,6 +29,7 @@ export const getTopNavLinks = ({ services, state, onOpenInspector, + searchSource, }: { getFieldCounts: () => Promise>; indexPattern: IndexPattern; @@ -38,6 +39,7 @@ export const getTopNavLinks = ({ services: DiscoverServices; state: GetStateReturn; onOpenInspector: () => void; + searchSource: ISearchSource; }) => { const newSearch = { id: 'new', @@ -93,7 +95,7 @@ export const getTopNavLinks = ({ return; } const sharingData = await getSharingData( - savedSearch.searchSource, + searchSource, state.appStateContainer.getState(), services.uiSettings, getFieldCounts diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.ts index 85acd575138f7..31de1f2f6ed66 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.ts @@ -9,7 +9,7 @@ import { Capabilities, IUiSettingsClient } from 'kibana/public'; import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common'; import { getSortForSearchSource } from '../angular/doc_table'; -import { SearchSource } from '../../../../data/common'; +import { ISearchSource } from '../../../../data/common'; import { AppState } from '../angular/discover_state'; import { SortOrder } from '../../saved_searches/types'; @@ -39,7 +39,7 @@ const getSharingDataFields = async ( * Preparing data to share the current state as link or CSV/Report */ export async function getSharingData( - currentSearchSource: SearchSource, + currentSearchSource: ISearchSource, state: AppState, config: IUiSettingsClient, getFieldCounts: () => Promise> diff --git a/src/plugins/discover/public/application/helpers/persist_saved_search.ts b/src/plugins/discover/public/application/helpers/persist_saved_search.ts index 06e90c93bc77c..2e4ab90ee58e5 100644 --- a/src/plugins/discover/public/application/helpers/persist_saved_search.ts +++ b/src/plugins/discover/public/application/helpers/persist_saved_search.ts @@ -35,7 +35,8 @@ export async function persistSavedSearch( state: AppState; } ) { - updateSearchSource(savedSearch.searchSource, { + updateSearchSource({ + persistentSearchSource: savedSearch.searchSource, indexPattern, services, sort: state.sort as SortOrder[], diff --git a/src/plugins/discover/public/application/helpers/update_search_source.test.ts b/src/plugins/discover/public/application/helpers/update_search_source.test.ts index 51586a6bccc23..97e2de3541d35 100644 --- a/src/plugins/discover/public/application/helpers/update_search_source.test.ts +++ b/src/plugins/discover/public/application/helpers/update_search_source.test.ts @@ -17,9 +17,12 @@ import { SortOrder } from '../../saved_searches/types'; describe('updateSearchSource', () => { test('updates a given search source', async () => { - const searchSourceMock = createSearchSourceMock({}); + const persistentSearchSourceMock = createSearchSourceMock({}); + const volatileSearchSourceMock = createSearchSourceMock({}); const sampleSize = 250; - const result = updateSearchSource(searchSourceMock, { + updateSearchSource({ + persistentSearchSource: persistentSearchSourceMock, + volatileSearchSource: volatileSearchSourceMock, indexPattern: indexPatternMock, services: ({ data: dataPluginMock.createStartContract(), @@ -36,15 +39,18 @@ describe('updateSearchSource', () => { columns: [], useNewFieldsApi: false, }); - expect(result.getField('index')).toEqual(indexPatternMock); - expect(result.getField('size')).toEqual(sampleSize); - expect(result.getField('fields')).toBe(undefined); + expect(persistentSearchSourceMock.getField('index')).toEqual(indexPatternMock); + expect(volatileSearchSourceMock.getField('size')).toEqual(sampleSize); + expect(volatileSearchSourceMock.getField('fields')).toBe(undefined); }); test('updates a given search source with the usage of the new fields api', async () => { - const searchSourceMock = createSearchSourceMock({}); + const persistentSearchSourceMock = createSearchSourceMock({}); + const volatileSearchSourceMock = createSearchSourceMock({}); const sampleSize = 250; - const result = updateSearchSource(searchSourceMock, { + updateSearchSource({ + persistentSearchSource: persistentSearchSourceMock, + volatileSearchSource: volatileSearchSourceMock, indexPattern: indexPatternMock, services: ({ data: dataPluginMock.createStartContract(), @@ -61,16 +67,19 @@ describe('updateSearchSource', () => { columns: [], useNewFieldsApi: true, }); - expect(result.getField('index')).toEqual(indexPatternMock); - expect(result.getField('size')).toEqual(sampleSize); - expect(result.getField('fields')).toEqual([{ field: '*' }]); - expect(result.getField('fieldsFromSource')).toBe(undefined); + expect(persistentSearchSourceMock.getField('index')).toEqual(indexPatternMock); + expect(volatileSearchSourceMock.getField('size')).toEqual(sampleSize); + expect(volatileSearchSourceMock.getField('fields')).toEqual([{ field: '*' }]); + expect(volatileSearchSourceMock.getField('fieldsFromSource')).toBe(undefined); }); test('requests unmapped fields when the flag is provided, using the new fields api', async () => { - const searchSourceMock = createSearchSourceMock({}); + const persistentSearchSourceMock = createSearchSourceMock({}); + const volatileSearchSourceMock = createSearchSourceMock({}); const sampleSize = 250; - const result = updateSearchSource(searchSourceMock, { + updateSearchSource({ + persistentSearchSource: persistentSearchSourceMock, + volatileSearchSource: volatileSearchSourceMock, indexPattern: indexPatternMock, services: ({ data: dataPluginMock.createStartContract(), @@ -88,16 +97,21 @@ describe('updateSearchSource', () => { useNewFieldsApi: true, showUnmappedFields: true, }); - expect(result.getField('index')).toEqual(indexPatternMock); - expect(result.getField('size')).toEqual(sampleSize); - expect(result.getField('fields')).toEqual([{ field: '*', include_unmapped: 'true' }]); - expect(result.getField('fieldsFromSource')).toBe(undefined); + expect(persistentSearchSourceMock.getField('index')).toEqual(indexPatternMock); + expect(volatileSearchSourceMock.getField('size')).toEqual(sampleSize); + expect(volatileSearchSourceMock.getField('fields')).toEqual([ + { field: '*', include_unmapped: 'true' }, + ]); + expect(volatileSearchSourceMock.getField('fieldsFromSource')).toBe(undefined); }); test('updates a given search source when showUnmappedFields option is set to true', async () => { - const searchSourceMock = createSearchSourceMock({}); + const persistentSearchSourceMock = createSearchSourceMock({}); + const volatileSearchSourceMock = createSearchSourceMock({}); const sampleSize = 250; - const result = updateSearchSource(searchSourceMock, { + updateSearchSource({ + persistentSearchSource: persistentSearchSourceMock, + volatileSearchSource: volatileSearchSourceMock, indexPattern: indexPatternMock, services: ({ data: dataPluginMock.createStartContract(), @@ -115,9 +129,11 @@ describe('updateSearchSource', () => { useNewFieldsApi: true, showUnmappedFields: true, }); - expect(result.getField('index')).toEqual(indexPatternMock); - expect(result.getField('size')).toEqual(sampleSize); - expect(result.getField('fields')).toEqual([{ field: '*', include_unmapped: 'true' }]); - expect(result.getField('fieldsFromSource')).toBe(undefined); + expect(persistentSearchSourceMock.getField('index')).toEqual(indexPatternMock); + expect(volatileSearchSourceMock.getField('size')).toEqual(sampleSize); + expect(volatileSearchSourceMock.getField('fields')).toEqual([ + { field: '*', include_unmapped: 'true' }, + ]); + expect(volatileSearchSourceMock.getField('fieldsFromSource')).toBe(undefined); }); }); diff --git a/src/plugins/discover/public/application/helpers/update_search_source.ts b/src/plugins/discover/public/application/helpers/update_search_source.ts index 55d2b05a29b69..ba5ac0e822796 100644 --- a/src/plugins/discover/public/application/helpers/update_search_source.ts +++ b/src/plugins/discover/public/application/helpers/update_search_source.ts @@ -15,24 +15,25 @@ import { DiscoverServices } from '../../build_services'; /** * Helper function to update the given searchSource before fetching/sharing/persisting */ -export function updateSearchSource( - searchSource: ISearchSource, - { - indexPattern, - services, - sort, - columns, - useNewFieldsApi, - showUnmappedFields, - }: { - indexPattern: IndexPattern; - services: DiscoverServices; - sort: SortOrder[]; - columns: string[]; - useNewFieldsApi: boolean; - showUnmappedFields?: boolean; - } -) { +export function updateSearchSource({ + indexPattern, + services, + sort, + columns, + useNewFieldsApi, + showUnmappedFields, + persistentSearchSource, + volatileSearchSource, +}: { + indexPattern: IndexPattern; + services: DiscoverServices; + sort: SortOrder[]; + columns: string[]; + useNewFieldsApi: boolean; + showUnmappedFields?: boolean; + persistentSearchSource: ISearchSource; + volatileSearchSource?: ISearchSource; +}) { const { uiSettings, data } = services; const usedSort = getSortForSearchSource( sort, @@ -40,23 +41,32 @@ export function updateSearchSource( uiSettings.get(SORT_DEFAULT_ORDER_SETTING) ); - searchSource + persistentSearchSource .setField('index', indexPattern) - .setField('size', uiSettings.get(SAMPLE_SIZE_SETTING)) - .setField('sort', usedSort) .setField('query', data.query.queryString.getQuery() || null) .setField('filter', data.query.filterManager.getFilters()); - if (useNewFieldsApi) { - searchSource.removeField('fieldsFromSource'); - const fields: Record = { field: '*' }; - if (showUnmappedFields) { - fields.include_unmapped = 'true'; + + if (volatileSearchSource) { + volatileSearchSource + .setField('size', uiSettings.get(SAMPLE_SIZE_SETTING)) + .setField('sort', usedSort) + .setField('highlightAll', true) + .setField('version', true) + // Even when searching rollups, we want to use the default strategy so that we get back a + // document-like response. + .setPreferredSearchStrategyId('default'); + + if (useNewFieldsApi) { + volatileSearchSource.removeField('fieldsFromSource'); + const fields: Record = { field: '*' }; + if (showUnmappedFields) { + fields.include_unmapped = 'true'; + } + volatileSearchSource.setField('fields', [fields]); + } else { + volatileSearchSource.removeField('fields'); + const fieldNames = indexPattern.fields.map((field) => field.name); + volatileSearchSource.setField('fieldsFromSource', fieldNames); } - searchSource.setField('fields', [fields]); - } else { - searchSource.removeField('fields'); - const fieldNames = indexPattern.fields.map((field) => field.name); - searchSource.setField('fieldsFromSource', fieldNames); } - return searchSource; } diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index c53dfaff24112..47161c2b8298e 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/src/plugins/vis_type_timeseries/common/metric_types.ts b/src/plugins/vis_type_timeseries/common/metric_types.ts index 52b31b6774371..17352f0f9da25 100644 --- a/src/plugins/vis_type_timeseries/common/metric_types.ts +++ b/src/plugins/vis_type_timeseries/common/metric_types.ts @@ -20,6 +20,8 @@ export enum METRIC_TYPES { VALUE_COUNT = 'value_count', AVERAGE = 'avg', SUM = 'sum', + MIN = 'min', + MAX = 'max', } // We should probably use BUCKET_TYPES from data plugin in future. diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx index d82a16f118621..579205ae04844 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx @@ -87,6 +87,14 @@ describe('TSVB AggSelect', () => { "label": "Count", "value": "count", }, + Object { + "label": "Max", + "value": "max", + }, + Object { + "label": "Min", + "value": "min", + }, Object { "label": "Sum", "value": "sum", diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx index 0b8fad4d780f6..00d088025bf25 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx @@ -225,7 +225,7 @@ const FILTER_RATIO_AGGS = [ 'value_count', ]; -const HISTOGRAM_AGGS = ['avg', 'count', 'sum', 'value_count']; +const HISTOGRAM_AGGS = ['avg', 'count', 'sum', 'min', 'max', 'value_count']; const allAggOptions = [...metricAggs, ...pipelineAggs, ...siblingAggs, ...specialAggs]; diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js index dd85e2490d70a..5648a8d3e7133 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.test.js @@ -51,26 +51,13 @@ describe('TSVB Filter Ratio', () => { field: 'histogram_value', }; const wrapper = setup(metric); - expect(wrapper.find(EuiComboBox).at(1).props().options).toMatchInlineSnapshot(` - Array [ - Object { - "label": "Average", - "value": "avg", - }, - Object { - "label": "Count", - "value": "count", - }, - Object { - "label": "Sum", - "value": "sum", - }, - Object { - "label": "Value Count", - "value": "value_count", - }, - ] - `); + expect( + wrapper + .find(EuiComboBox) + .at(1) + .props() + .options.map(({ value }) => value) + ).toEqual(['avg', 'count', 'max', 'min', 'sum', 'value_count']); }); const shouldNotHaveHistogramField = (agg) => { it(`should not have histogram fields for ${agg}`, () => { @@ -80,23 +67,19 @@ describe('TSVB Filter Ratio', () => { field: '', }; const wrapper = setup(metric); - expect(wrapper.find(EuiComboBox).at(2).props().options).toMatchInlineSnapshot(` - Array [ - Object { - "label": "number", - "options": Array [ - Object { - "label": "system.cpu.user.pct", - "value": "system.cpu.user.pct", - }, - ], - }, - ] - `); + expect(wrapper.find(EuiComboBox).at(2).props().options).toEqual([ + { + label: 'number', + options: [ + { + label: 'system.cpu.user.pct', + value: 'system.cpu.user.pct', + }, + ], + }, + ]); }); }; - shouldNotHaveHistogramField('max'); - shouldNotHaveHistogramField('min'); shouldNotHaveHistogramField('positive_rate'); it(`should not have histogram fields for cardinality`, () => { diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js index 5a5f37db2b7b5..c536856327f28 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/histogram_support.test.js @@ -65,6 +65,8 @@ describe('Histogram Types', () => { }; shouldHaveHistogramSupport('avg'); shouldHaveHistogramSupport('sum'); + shouldHaveHistogramSupport('min'); + shouldHaveHistogramSupport('max'); shouldHaveHistogramSupport('value_count'); shouldHaveHistogramSupport('percentile'); shouldHaveHistogramSupport('percentile_rank'); @@ -81,8 +83,6 @@ describe('Histogram Types', () => { ); }; shouldNotHaveHistogramSupport('cardinality'); - shouldNotHaveHistogramSupport('max'); - shouldNotHaveHistogramSupport('min'); shouldNotHaveHistogramSupport('variance'); shouldNotHaveHistogramSupport('sum_of_squares'); shouldNotHaveHistogramSupport('std_deviation'); diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.js index ba3b330ed8eef..61226e4e8dcbe 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.js @@ -17,6 +17,8 @@ export function getSupportedFieldsByMetricType(type) { return Object.values(KBN_FIELD_TYPES); case METRIC_TYPES.AVERAGE: case METRIC_TYPES.SUM: + case METRIC_TYPES.MIN: + case METRIC_TYPES.MAX: return [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.HISTOGRAM]; default: return [KBN_FIELD_TYPES.NUMBER]; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.test.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.test.js index 10258effbb883..c009146abb7bd 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.test.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/get_supported_fields_by_metric_type.test.js @@ -26,11 +26,11 @@ describe('getSupportedFieldsByMetricType', () => { shouldSupportAllFieldTypes('value_count'); shouldHaveHistogramAndNumbers('avg'); shouldHaveHistogramAndNumbers('sum'); + shouldHaveHistogramAndNumbers('min'); + shouldHaveHistogramAndNumbers('max'); shouldHaveOnlyNumbers('positive_rate'); shouldHaveOnlyNumbers('std_deviation'); - shouldHaveOnlyNumbers('max'); - shouldHaveOnlyNumbers('min'); it(`should return everything but histogram for cardinality`, () => { expect(getSupportedFieldsByMetricType('cardinality')).not.toContain('histogram'); 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 0c1ab262755a7..c9ed82fcf58e5 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 9f557806cf142..5c28ca77da0c4 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 80e7e12adf799..5028bc379c375 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 c7ba5021196ff..ab398101bac9d 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/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index f731ffade6efc..5bf99b4bf1136 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 4faf645975c77..58dcc9f52c089 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 a483f8378b8b4..3300ed2d0884f 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 648605135b359..0198a5d0ac5fa 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" diff --git a/x-pack/plugins/alerts/README.md b/x-pack/plugins/alerts/README.md index 2191b23eec11e..aab848d4555d2 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. 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 0000000000000..546c8627def69 --- /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 3dea3344085c9..dbbbf324b964a 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/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx index 3edf21eae7279..4cd2db43621a8 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/RumDashboard/ClientMetrics/Metrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/Metrics.tsx new file mode 100644 index 0000000000000..3a9100a0712aa --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/Metrics.tsx @@ -0,0 +1,139 @@ +/* + * 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 React from 'react'; +import numeral from '@elastic/numeral'; +import styled from 'styled-components'; +import { useContext, useEffect } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiStat, + EuiToolTip, + EuiIconTip, +} from '@elastic/eui'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { I18LABELS } from '../translations'; +import { useUxQuery } from '../hooks/useUxQuery'; +import { formatToSec } from '../UXMetrics/KeyUXMetrics'; +import { CsmSharedContext } from '../CsmSharedContext'; + +const ClFlexGroup = styled(EuiFlexGroup)` + flex-direction: row; + @media only screen and (max-width: 768px) { + flex-direction: row; + justify-content: space-between; + } +`; + +function formatTitle(unit: string, value?: number) { + if (typeof value === 'undefined') return I18LABELS.dataMissing; + return formatToSec(value, unit); +} + +function PageViewsTotalTitle({ pageViews }: { pageViews?: number }) { + if (typeof pageViews === 'undefined') { + return <>{I18LABELS.dataMissing}; + } + return pageViews < 10000 ? ( + <>{numeral(pageViews).format('0,0')} + ) : ( + + <>{numeral(pageViews).format('0 a')} + + ); +} + +export function Metrics() { + const uxQuery = useUxQuery(); + + const { data, status } = useFetcher( + (callApmApi) => { + if (uxQuery) { + return callApmApi({ + endpoint: 'GET /api/apm/rum/client-metrics', + params: { + query: { + ...uxQuery, + }, + }, + }); + } + return Promise.resolve(null); + }, + [uxQuery] + ); + + const { setSharedData } = useContext(CsmSharedContext); + + useEffect(() => { + setSharedData({ totalPageViews: data?.pageViews?.value ?? 0 }); + }, [data, setSharedData]); + + const STAT_STYLE = { minWidth: '150px', maxWidth: '250px' }; + + return ( + + + + {I18LABELS.totalPageLoad} + + + } + isLoading={status !== 'success'} + /> + + + + {I18LABELS.backEnd} + + + } + isLoading={status !== 'success'} + /> + + + + {I18LABELS.frontEnd} + + + } + isLoading={status !== 'success'} + /> + + + } + description={I18LABELS.pageViews} + isLoading={status !== 'success'} + /> + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index 1d6ce21d198c4..add6ac1b08b28 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -6,134 +6,36 @@ */ import * as React from 'react'; -import numeral from '@elastic/numeral'; -import styled from 'styled-components'; -import { useContext, useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem, - EuiStat, - EuiToolTip, - EuiIconTip, + EuiPanel, + EuiTitle, + EuiSpacer, } from '@elastic/eui'; -import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; -import { useUxQuery } from '../hooks/useUxQuery'; -import { formatToSec } from '../UXMetrics/KeyUXMetrics'; -import { CsmSharedContext } from '../CsmSharedContext'; - -const ClFlexGroup = styled(EuiFlexGroup)` - flex-direction: row; - @media only screen and (max-width: 768px) { - flex-direction: row; - justify-content: space-between; - } -`; - -function formatTitle(unit: string, value?: number) { - if (typeof value === 'undefined') return I18LABELS.dataMissing; - return formatToSec(value, unit); -} - -function PageViewsTotalTitle({ pageViews }: { pageViews?: number }) { - if (typeof pageViews === 'undefined') { - return <>{I18LABELS.dataMissing}; - } - return pageViews < 10000 ? ( - <>{numeral(pageViews).format('0,0')} - ) : ( - - <>{numeral(pageViews).format('0 a')} - - ); -} +import { getPercentileLabel } from '../UXMetrics/translations'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { Metrics } from './Metrics'; export function ClientMetrics() { - const uxQuery = useUxQuery(); - - const { data, status } = useFetcher( - (callApmApi) => { - if (uxQuery) { - return callApmApi({ - endpoint: 'GET /api/apm/rum/client-metrics', - params: { - query: { - ...uxQuery, - }, - }, - }); - } - return Promise.resolve(null); - }, - [uxQuery] - ); - - const { setSharedData } = useContext(CsmSharedContext); - - useEffect(() => { - setSharedData({ totalPageViews: data?.pageViews?.value ?? 0 }); - }, [data, setSharedData]); - - const STAT_STYLE = { width: '240px' }; + const { + urlParams: { percentile }, + } = useUrlParams(); return ( - - - - {I18LABELS.totalPageLoad} - - - } - isLoading={status !== 'success'} - /> - - - - {I18LABELS.backEnd} - - - } - isLoading={status !== 'success'} - /> - - - - {I18LABELS.frontEnd} - - - } - isLoading={status !== 'success'} - /> - - - } - description={I18LABELS.pageViews} - isLoading={status !== 'success'} - /> - - + + + + +

+ {I18LABELS.pageLoad} ({getPercentileLabel(percentile!)}) +

+
+ + +
+
+
); } diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx index 4afecb7623f73..5b0b475e86e03 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/LocalUIFilters/index.tsx @@ -11,12 +11,14 @@ import { EuiSpacer, EuiHorizontalRule, EuiButtonEmpty, + EuiAccordion, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { Filter } from './Filter'; import { useLocalUIFilters } from '../hooks/useLocalUIFilters'; import { LocalUIFilterName } from '../../../../../common/ui_filter'; +import { useBreakPoints } from '../../../../hooks/use_break_points'; interface Props { filterNames: LocalUIFilterName[]; @@ -45,16 +47,20 @@ function LocalUIFilters({ const hasValues = filters.some((filter) => filter.value.length > 0); - return ( + const { isSmall } = useBreakPoints(); + + const title = ( + +

+ {i18n.translate('xpack.apm.localFiltersTitle', { + defaultMessage: 'Filters', + })} +

+
+ ); + + const content = ( <> - -

- {i18n.translate('xpack.apm.localFiltersTitle', { - defaultMessage: 'Filters', - })} -

-
- {children} {filters.map((filter) => { return ( @@ -90,6 +96,18 @@ function LocalUIFilters({ ) : null} ); + + return isSmall ? ( + + {content} + + ) : ( + <> + {title} + + {content} + + ); } export { LocalUIFilters }; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx index 7d34328d2653d..e3e2a979c48d3 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx @@ -13,6 +13,7 @@ import { useFetcher } from '../../../../hooks/use_fetcher'; import { RUM_AGENT_NAMES } from '../../../../../common/agent_name'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { UserPercentile } from '../UserPercentile'; +import { useBreakPoints } from '../../../../hooks/use_break_points'; export function MainFilters() { const { @@ -37,6 +38,11 @@ export function MainFilters() { [start, end] ); + const { isSmall } = useBreakPoints(); + + // on mobile we want it to take full width + const envStyle = isSmall ? {} : { maxWidth: 200 }; + return ( <> @@ -45,7 +51,7 @@ export function MainFilters() { serviceNames={data ?? []} /> - + diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx index 50d91ca73c249..a182de8540f58 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx @@ -5,46 +5,22 @@ * 2.0. */ -import { - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiSpacer, - EuiPanel, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; -import { ClientMetrics } from './ClientMetrics'; -import { I18LABELS } from './translations'; import { UXMetrics } from './UXMetrics'; import { ImpactfulMetrics } from './ImpactfulMetrics'; import { PageLoadAndViews } from './Panels/PageLoadAndViews'; import { VisitorBreakdownsPanel } from './Panels/VisitorBreakdowns'; import { useBreakPoints } from '../../../hooks/use_break_points'; -import { getPercentileLabel } from './UXMetrics/translations'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { ClientMetrics } from './ClientMetrics'; export function RumDashboard() { - const { - urlParams: { percentile }, - } = useUrlParams(); const { isSmall } = useBreakPoints(); return ( - - - - -

- {I18LABELS.pageLoad} ({getPercentileLabel(percentile!)}) -

-
- - -
-
-
+
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx index 6e3ed54ed2e78..a0b3781a30b20 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx @@ -20,24 +20,15 @@ export const UX_LABEL = i18n.translate('xpack.apm.ux.title', { export function RumHome() { return ( - +

{UX_LABEL}

+ - - - - - - +
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx index d3a34a1df25f7..965449b78f3e0 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx @@ -206,6 +206,7 @@ export function SelectableUrlList({ panelRef={setPopoverRef} button={search} closePopover={closePopover} + style={{ minWidth: 200 }} >
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx index f2e761128e5ce..b8766e8b5ce67 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx @@ -36,7 +36,7 @@ export function formatToSec( } return (valueInMs / 1000).toFixed(2) + ' s'; } -const STAT_STYLE = { width: '240px' }; +const STAT_STYLE = { width: '200px' }; interface Props { data?: UXMetrics | null; @@ -71,7 +71,7 @@ export function KeyUXMetrics({ data, loading }: Props) { // Note: FCP value is in ms unit return ( - + - +

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 7386209310c1f..9b80ee6fc31b8 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 c88aaa85bb856..459df99a62f5a 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 29bdf6467e544..bde23eddaa44f 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 1cb420a8ac194..cd17ca0ce023d 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(() => { @@ -118,7 +123,7 @@ export function ServiceInventory() { return ( <> - + {displayMlCallout ? ( 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 0000000000000..fda45db98d0ca --- /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/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index db0b8283a28c8..a0ea00ae5c3ad 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -57,11 +57,7 @@ export function ServiceOverview({ return ( - + 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 baae683e2eba9..a4647bc148b1e 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, @@ -96,7 +93,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { name: i18n.translate( 'xpack.apm.serviceOverview.dependenciesTableColumnLatency', { - defaultMessage: 'Latency', + defaultMessage: 'Latency (avg.)', } ), width: px(unit * 10), @@ -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 7f4823c13d593..f7f5db32e986c 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 e7c1d4442e3b7..2f2aaf3156b93 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: { start, end }, + urlParams: { environment, latencyAggregationType, start, end }, uiFilters, } = useUrlParams(); const { data = [], status } = useFetcher( (callApmApi) => { - if (!start || !end || !transactionType) { + if (!start || !end || !transactionType || !latencyAggregationType) { return; } @@ -43,6 +43,8 @@ export function ServiceOverviewInstancesChartAndTable({ serviceName, }, query: { + environment, + latencyAggregationType, start, end, transactionType, @@ -52,7 +54,15 @@ export function ServiceOverviewInstancesChartAndTable({ }, }); }, - [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 62ae4b7bc3446..83ad506e8659b 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 ( (); const { urlParams, uiFilters } = useUrlParams(); const { transactionType } = useApmServiceContext(); - const { start, end, comparisonEnabled, comparisonType } = urlParams; - const comparisonChartTheme = getComparisonChartTheme(theme); - const { - comparisonStart = undefined, - comparisonEnd = undefined, - } = comparisonType - ? getTimeRangeComparison({ - start, - end, - comparisonType, - }) - : {}; + const { environment, start, end } = urlParams; const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { @@ -57,26 +42,17 @@ export function ServiceOverviewThroughputChart({ serviceName, }, query: { + environment, start, end, transactionType, uiFilters: JSON.stringify(uiFilters), - comparisonStart, - comparisonEnd, }, }, }); } }, - [ - serviceName, - start, - end, - uiFilters, - transactionType, - comparisonStart, - comparisonEnd, - ] + [environment, serviceName, start, end, uiFilters, transactionType] ); return ( @@ -93,7 +69,6 @@ export function ServiceOverviewThroughputChart({ height={height} showAnnotations={false} fetchStatus={status} - customTheme={comparisonChartTheme} timeseries={[ { data: data.currentPeriod, @@ -104,21 +79,6 @@ export function ServiceOverviewThroughputChart({ { defaultMessage: 'Throughput' } ), }, - ...(comparisonEnabled - ? [ - { - data: data.previousPeriod, - type: 'area', - color: theme.eui.euiColorLightestShade, - title: i18n.translate( - 'xpack.apm.serviceOverview.throughtputChart.previousPeriodLabel', - { - defaultMessage: 'Previous period', - } - ), - }, - ] - : []), ]} yLabelFormat={asTransactionRate} /> diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx index 2ffc0fc9c93a3..b01ad93f2c998 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -9,6 +9,7 @@ import { EuiBasicTableColumn } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { ValuesType } from 'utility-types'; +import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { asMillisecondDuration, asPercent, @@ -20,6 +21,7 @@ import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ImpactBar } from '../../../shared/ImpactBar'; import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; +import { getLatencyColumnLabel } from '../get_latency_column_label'; type TransactionGroupPrimaryStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics'>; @@ -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/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 a0facb2ddbedf..5529f9028b9dd 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 cae0ef2de2ad1..8fc9ac12824ba 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_details/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx index a6fe2b170bc3b..0a322cfc9c80b 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx @@ -95,7 +95,7 @@ export function TransactionDetails({

{transactionName}

- + diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 720a3857ef520..97be35ec6f5b9 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -82,7 +82,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { return ( <> - + diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx index 8fb5166bd8676..e4fbd07566060 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx @@ -131,7 +131,7 @@ describe('TransactionOverview', () => { }); expect(history.location.search).toEqual( - '?transactionType=secondType&rangeFrom=now-15m&rangeTo=now&comparisonEnabled=true&comparisonType=day' + '?transactionType=secondType&rangeFrom=now-15m&rangeTo=now' ); expect(getByText(container, 'firstType')).toBeInTheDocument(); expect(getByText(container, 'secondType')).toBeInTheDocument(); @@ -142,7 +142,7 @@ describe('TransactionOverview', () => { expect(history.push).toHaveBeenCalled(); expect(history.location.search).toEqual( - '?transactionType=firstType&rangeFrom=now-15m&rangeTo=now&comparisonEnabled=true&comparisonType=day' + '?transactionType=firstType&rangeFrom=now-15m&rangeTo=now' ); }); }); 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 406ba98b79e25..a63788457b8b5 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 714645fd74961..59c99463144cb 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/timeseries_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx index 7bfe17e82bf4a..8009f288d48c0 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/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 466f201ab3398..293a1929ca606 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 df18e7627faed..a3da8812966f1 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/helpers.test.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts index bdce54ad08634..4de68a5bc2036 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 f77c4119f46e1..eae9eba8b3dda 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/resolve_url_params.ts b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts index 63d719205c2ad..b6e7330be30cb 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.test.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx index 66e22f32b26bd..056aabb10f878 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', ]); }); }); 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 8312fedc7eb03..90245b9843b01 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 834b0cc052789..9ff179e6af6a0 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 ecfa5471189d2..87e10f1e8937b 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 66446bf0dfeba..c493a30716aa5 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 d5974ee3543a7..cca2e99d84dfd 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 af9a5fee24877..55765cd40c04e 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 aac6196c4253c..f7f6f7486091b 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 e487684909633..3457207eeee3c 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 05ad743af0997..aa85c44284d9d 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 fa01773c78070..88e249a71a81f 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 e9e2e078ec344..c7861eaa819ae 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 480efd8d4c7ad..704aee932a604 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 882bde8792761..6f58b7714d832 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 a560f011b186b..d70e19bf4a5f5 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 721e35e2ef60d..ecefdfc2b3d9b 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 816061da5cfc1..832b89a18d102 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_all_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts index 8fedcf6224e3c..1bf01c24776fb 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/environments/get_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_environments.ts index 56f0a03910c1a..af88493c148ce 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 db8414864f577..1712699162b73 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 383fcbb2f5ce7..fbe406d8d1a9d 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 be3a29780a5b6..1fb0cbad4a5f0 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 121b9b3d0c46f..0ab26f3c6e969 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 6e91f8fe7cdd2..28d89eb057470 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 424c85d55a36e..71744c3e59092 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 57bf511f45942..0000000000000 --- 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 63e0edd6097bf..e91c9b52deecf 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 961a1eee61d1d..4eed09f3e5c28 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 e03be2391597d..c5e80600b69d4 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 e76ad10b535b0..d7c9294c8ec7a 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 7989f57046ae7..8c5b9fb3db922 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 446894f82b75e..98f31f06c1b64 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 2b7bb9ea8da6e..d6cbc4a07e8f9 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 e137720000262..970b4d3499b79 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 a3e253d2c81d6..25abd2c34c83a 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 e176c156ad05a..c8a209fee701a 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 e7f576b73c5ae..ebfe504e5269b 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 0f7954d86d3e2..55b3328bcd2a9 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 7a52806601e0e..17e9aef29ba82 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 5083982f1cb9c..eda71ef380ee9 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 4af57a685bf83..c7ac678899b58 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 87394567afc50..2da4b0f8de363 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 368c5ec546359..9626019347e5b 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 939ebbb1f7941..259a0e6daea6f 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 2c64678eb082e..ab221e30ea489 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 951484308db19..1aee1bb5b242a 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 ef4faf9406346..6e9225041b199 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 63f28abab8f3a..b161345e729d3 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 213702bf06f4c..e384b15685dad 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 deb9104a83905..e8dcb28baa9a3 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 d83e558775be4..e6d702cc03c0b 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/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index 606ce87035156..3e68831ee7cba 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/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts index 7c746aac29af9..67aa9d7fcd870 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 fc53f763dac0f..6c7cbc26ea653 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 29c77da6e4075..3683a069342a9 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 ef2b50cbdbedf..558d6ae22f00f 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), ], }, }, @@ -71,14 +70,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 +91,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, }; }) ?? []; @@ -123,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 9a020daa7e095..dfbdfb3f504e8 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 19f306f5cb803..724b5278d7edf 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 f5aa01e1dfa58..a17fb6da2007f 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 4f8088352d0ae..ef90e5197229b 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 2cbe5a42206d1..620fd9828bd37 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, @@ -22,8 +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, @@ -45,11 +51,7 @@ export async function getServiceInstanceTransactionStats({ ); const subAggs = { - avg_transaction_duration: { - avg: { - field, - }, - }, + ...getLatencyAggregation(latencyAggregationType, field), failures: { filter: { term: { @@ -72,9 +74,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, ], }, @@ -115,7 +118,7 @@ export async function getServiceInstanceTransactionStats({ (serviceNodeBucket) => { const { doc_count: count, - avg_transaction_duration: avgTransactionDuration, + latency, key, failures, timeseries, @@ -138,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 021774f9522c1..7c0124f4ce004 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'; @@ -12,6 +13,8 @@ import { getServiceInstanceSystemMetricStats } from './get_service_instance_syst import { getServiceInstanceTransactionStats } from './get_service_instance_transaction_stats'; export interface ServiceInstanceParams { + environment?: string; + latencyAggregationType: LatencyAggregationType; 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 f1198a4d858fd..5c43191cf588c 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 0ea95a08abaa9..b342ffea02464 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 54cf89d6125b6..ce36db3e82bab 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 168eed8e38374..ddbfd617faf65 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 bc4660e2c01a5..3d77bf5bd6baf 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 e88fafb061912..6fc868b0f0a4e 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 36540b01a07cc..e1f8bca83829c 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, ], }, }, @@ -98,10 +101,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 +144,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/services/get_services/get_services_items.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts index 1ba9aaf980201..c2677af038486 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 45efd80c45a98..1a0ddeda11651 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 33268e9b3332d..f7cd23b0e37a7 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 bd3ecf1e0f862..f631657f87276 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 89069d74bacf8..7fb2bb2fcbeeb 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", + }, }, }, }, @@ -240,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 { @@ -254,8 +254,12 @@ Array [ }, }, Object { - "term": Object { - "service.name": "foo", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, }, }, Object { @@ -295,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 { @@ -309,8 +309,12 @@ Array [ }, }, Object { - "term": Object { - "service.name": "foo", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, }, }, Object { @@ -350,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 { @@ -364,8 +364,12 @@ Array [ }, }, Object { - "term": Object { - "service.name": "foo", + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, }, }, Object { @@ -411,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 { @@ -425,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 0fad948edde19..09e5e358a1b7c 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 839efc9009c38..d1a056002db07 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/transaction_groups/get_transaction_group_stats.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts index ee19a6a8d1591..5ee46bf1a5918 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/breakdown/index.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts index 8a2579b4a2b87..c3741184c807d 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 22a34ded4c20d..7ed016cd4b4c6 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, ]; @@ -109,8 +115,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 +137,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/lib/transactions/distribution/get_distribution_max.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts index ed54dae32704e..f8061ea989469 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 22436cac40183..92d1d96b4a8e3 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 002ddd1ec35f0..d566f3a169e78 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 29dd562330cc1..a03b1ac82e90a 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 ee27d00fdc0d4..e1d3921d298c7 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 7c1fa9c3a2368..ec5dbf0eab3e9 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 2fdb8e25fd996..38d6b593dc72d 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 082fd53a0ca93..342d78608efbf 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 f6c3f85ed4807..a32c2ae46c870 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 ff8d868bc4abe..1d5f7316b69ad 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 33ffb45a00637..a9f5a7efd0e67 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 76f2fc164e3fd..45ed5d2865a67 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 3c2ff00153ce7..d4a0db3c0d6c7 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 fc5d6a3dd0bcd..822a45fca269f 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 fdc1e8ebe5a55..990b462a520d2 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 cc9db2e6a4855..073a91bfe1548 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 d07504a1046ee..08376ed0e37ff 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 7cca6cd0a1943..65c7b245958f3 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 ff064e0571d13..24c7c6e3e23d7 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -25,23 +25,33 @@ 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'; +import { + latencyAggregationTypeRt, + LatencyAggregationType, +} from '../../common/latency_aggregation_types'; 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 +283,7 @@ export const serviceErrorGroupsRoute = createRoute({ serviceName: t.string, }), query: t.intersection([ + environmentRt, rangeRt, uiFiltersRt, t.type({ @@ -296,6 +307,7 @@ export const serviceErrorGroupsRoute = createRoute({ const { path: { serviceName }, query: { + environment, numBuckets, pageIndex, size, @@ -304,7 +316,9 @@ export const serviceErrorGroupsRoute = createRoute({ transactionType, }, } = context.params; + return getServiceErrorGroups({ + environment, serviceName, setup, size, @@ -325,6 +339,7 @@ export const serviceThroughputRoute = createRoute({ }), query: t.intersection([ t.type({ transactionType: t.string }), + environmentRt, uiFiltersRt, rangeRt, comparisonRangeRt, @@ -335,6 +350,7 @@ export const serviceThroughputRoute = createRoute({ const setup = await setupRequest(context, request); const { serviceName } = context.params.path; const { + environment, transactionType, comparisonStart, comparisonEnd, @@ -355,12 +371,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) => @@ -387,7 +405,12 @@ 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, ]), @@ -396,13 +419,17 @@ 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 latencyAggregationType = (context.params.query + .latencyAggregationType as unknown) as LatencyAggregationType; const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup ); return getServiceInstances({ + environment, + latencyAggregationType, serviceName, setup, transactionType, @@ -420,9 +447,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 722675906487c..5d3f99be7af34 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 bef96cb7f0767..5a4be216a817c 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 19ac562121d9d..4df638cc2c5df 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/plugins/case/common/api/cases/case.ts b/x-pack/plugins/case/common/api/cases/case.ts index 49643ca1f4d0c..33a93952b0e2d 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 cfc6099fa4bb5..41ad0e87f14d2 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 de9e88993df9a..6c8e0de80903d 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 9c290c0a4d612..00c8ff402c802 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 5d34ed120ff6f..cc69c7ecc2909 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 065825472954b..3016a57f21875 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 eab43a0c4d453..ab0b97abbcb76 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 2be9f41059831..809c4ad1ea1bd 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 53e233c74deb4..7a3e4458f25c5 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 78bdc6d282c69..fda4142bf77c7 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 315203a1f5e1d..c9b1e4fd13272 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 7dd1b4a8f6c5c..0a86c1825fedc 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 a8f64227daf83..ba5677426c222 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 8a4e45f71b9ca..f6371b8e8b1e7 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 d89feb009f806..5e6a86358de25 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 6b7e395bae4dc..4be519858db18 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 34b407616cfe4..a64cba567ce46 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 cdeb00209f846..ac34ad40cfa13 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 056ccff2733a7..898d61301a140 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 2fe0be3e08ede..e67a6f6dd3344 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 9dec910f9fc46..1ebd336c83af7 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 fb51b8f76d0ef..807ec0d089a52 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 e50d14e5c66c4..b3f87211c9547 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 53829157c5b04..e1669203d3ded 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 06e929cc40e6b..488f32a795811 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 f2fd986dd8a3a..12d1da36077c7 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 1efec927efb62..f6bc1e4f71897 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 bc82f656f477b..084b1a17a1434 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 9eabf744f2e13..a4fdc24b6e4ee 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 a0b22c49d0bc6..21ef27de1ec85 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 2ea9718d18487..9dd577c40c74e 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 320d32ac0d788..a19e533418bc9 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 091775827c6a6..d05ada0dba30c 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/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/box.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/box.svg new file mode 100644 index 0000000000000..827f8cf0a55ec --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/box.svg @@ -0,0 +1 @@ + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts index 1c66b7cf1758c..d9a0975abef7c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import box from './box.svg'; import confluence from './confluence.svg'; import custom from './custom.svg'; import dropbox from './dropbox.svg'; @@ -21,6 +22,7 @@ import slack from './slack.svg'; import zendesk from './zendesk.svg'; export const imagesFull = { + box, confluence, confluenceCloud: confluence, confluenceServer: confluence, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts index 4fd4d480108de..cdfd07b07de91 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts @@ -679,6 +679,10 @@ export const DESCRIPTION_LABEL = i18n.translate( } ); +export const AND = i18n.translate('xpack.enterpriseSearch.workplaceSearch.and', { + defaultMessage: 'and', +}); + export const UPDATE_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearch.update.label', { defaultMessage: 'Update', }); diff --git a/x-pack/plugins/security_solution/public/cases/components/property_actions/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts similarity index 81% rename from x-pack/plugins/security_solution/public/cases/components/property_actions/constants.ts rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts index e23bae6f2403e..37228cf9e7025 100644 --- a/x-pack/plugins/security_solution/public/cases/components/property_actions/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export const SET_STATE = 'SET_STATE'; +export { toSentenceSerial } from './to_sentence_serial'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.test.ts new file mode 100644 index 0000000000000..fb6501d3cb943 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.test.ts @@ -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 { toSentenceSerial } from './to_sentence_serial'; + +describe('toSentenceSerial', () => { + it('works correctly for 1 word', () => { + expect(toSentenceSerial(['One'])).toEqual('One'); + }); + + it('works correctly for 2 words', () => { + expect(toSentenceSerial(['One', 'Two'])).toEqual('One and Two'); + }); + + it('works correctly for 3+ words', () => { + expect(toSentenceSerial(['One', 'Two', 'Three'])).toEqual('One, Two, and Three'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.ts new file mode 100644 index 0000000000000..ad6383a76adc2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/to_sentence_serial.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AND } from '../constants'; + +export const toSentenceSerial = (array: string[]) => + array.length === 1 + ? array[0] + : `${array.slice(0, array.length - 1).join(', ')}${ + array.length === 2 ? '' : ',' + } ${AND} ${array.slice(-1)}`; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.scss b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.scss new file mode 100644 index 0000000000000..fbc10b5e8ed0f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.scss @@ -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. + */ + +// -------------------------------------------------- +// View: Adding a Source flow +// -------------------------------------------------- + +.adding-a-source { + ul { + padding: 0; + } + + li + li { + margin-top: auto !important; + } + + &__acl-tooltip { + cursor: not-allowed; + + .euiSwitch__label { + cursor: not-allowed; + } + } + + &__icon { + width: 3.5em; + height: 3.5em; + } + + &__category { + text-transform: capitalize; + + &:after { + content: ', '; + } + + &:last-child:after { + content: ''; + } + } + + &__outer-box { + border: 1px solid #DBE2EB; + padding-right: 16px; + border-radius: 6px; + overflow: hidden; + background-color: #FFFFFF; + box-shadow: 0 2px 2px -1px rgba(152, 162, 179, .3), + 0 1px 5px -2px rgba(152, 162, 179, .3); + } + + &__intro-image { + background-color: #22272E; + display: flex; + justify-content: center; + align-items: center; + height: 100%; + } + + &__intro-image img { + width: auto; + height: auto; + } + + &__intro-step { + width: 80px; + height: 100%; + display: flex; + background: transparent; + border-radius: 0; + border-right: 2px solid #DBE2EB; + padding: 0 18px 0 0; + text-align: center; + justify-content: center; + align-items: center; + } + + &__config-steps { + p { + margin: 0; + } + } + + &__button-row { + .euiFlexItem:first-child { + align-self: flex-end; + } + .euiFlexItem:last-child { + align-self: flex-start; + } + } + + &__connect-an-instance { + flex-basis: auto; + } + + &__features-list { + flex-basis: 100%; + } + + &__feature-image { + width: 2.5em; + margin: 0; + background: transparent; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx index b00f9807f0acd..64431a800487f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx @@ -28,6 +28,8 @@ import { ReAuthenticate } from './re_authenticate'; import { SaveConfig } from './save_config'; import { SaveCustom } from './save_custom'; +import './add_source.scss'; + export const AddSource: React.FC = (props) => { const { initializeAddSource, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx index f12c24feb8e1a..bf472240d3c89 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx @@ -52,7 +52,6 @@ export const AddSourceHeader: React.FC = ({
- ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx index 914eee74dfc4e..917886d69bd19 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx @@ -45,6 +45,7 @@ export const ConfigurationIntro: React.FC = ({ }) => (
{header} + = ({ return (
{header} +
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.test.tsx index b795b0af09944..d6b427630e48e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.test.tsx @@ -12,9 +12,8 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiBadge, EuiCallOut, EuiSwitch } from '@elastic/eui'; +import { EuiSwitch } from '@elastic/eui'; -import { FeatureIds } from '../../../../types'; import { staticSourceData } from '../../source_data'; import { ConnectInstance } from './connect_instance'; @@ -46,7 +45,6 @@ describe('ConnectInstance', () => { const credentialsSourceData = staticSourceData[13]; const oauthSourceData = staticSourceData[0]; const subdomainSourceData = staticSourceData[16]; - const privateSourceData = staticSourceData[6]; const props = { ...credentialsSourceData, @@ -128,22 +126,6 @@ describe('ConnectInstance', () => { expect(setSourceSubdomainValue).toHaveBeenCalledWith(TEXT); }); - it('shows correct feature badges', () => { - setMockValues({ ...values, isOrganization: false }); - const wrapper = shallow(); - - expect(wrapper.find(EuiBadge)).toHaveLength(2); - }); - - it('shows no feature badges', () => { - setMockValues({ ...values, isOrganization: false }); - const features = { ...privateSourceData.features }; - features.platinumPrivateContext = [FeatureIds.SyncFrequency]; - const wrapper = shallow(); - - expect(wrapper.find(EuiBadge)).toHaveLength(0); - }); - it('calls handler on click', () => { const wrapper = shallow(); wrapper.find(EuiSwitch).simulate('change', { target: { checked: true } }); @@ -163,16 +145,23 @@ describe('ConnectInstance', () => { expect(mockReplace).toHaveBeenCalled(); }); - it('renders permissions link', () => { - const wrapper = shallow(); + it('renders doc-level permissions message when not available', () => { + const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="NeedsPermissionsMessage"]')).toHaveLength(1); + expect(wrapper.find('FormattedMessage')).toHaveLength(1); }); - it('shows permissions callout', () => { + it('renders callout when not synced', () => { setMockValues({ ...values, indexPermissionsValue: false }); - const wrapper = shallow(); + const wrapper = shallow(); + + expect(wrapper.find('EuiCallOut')).toHaveLength(1); + }); + + it('renders documentLevelPermissionsCallout', () => { + setMockValues({ ...values, hasPlatinumLicense: false }); + const wrapper = shallow(); - expect(wrapper.find(EuiCallOut)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="DocumentLevelPermissionsCallout"]')).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx index 08b29075f3d0d..2290a65912797 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx @@ -12,38 +12,39 @@ import { useActions, useValues } from 'kea'; import { EuiButton, EuiCallOut, + EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiFieldText, EuiFormRow, + EuiHorizontalRule, + EuiIcon, EuiLink, + EuiPanel, EuiSpacer, EuiSwitch, EuiText, EuiTitle, - EuiTextColor, - EuiBadge, - EuiBadgeGroup, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { LicensingLogic } from '../../../../../shared/licensing'; import { AppLogic } from '../../../../app_logic'; -import { DOCUMENT_PERMISSIONS_DOCS_URL } from '../../../../routes'; +import { DOCUMENT_PERMISSIONS_DOCS_URL, ENT_SEARCH_LICENSE_MANAGEMENT } from '../../../../routes'; import { FeatureIds, Configuration, Features } from '../../../../types'; import { LEARN_MORE_LINK } from '../../constants'; import { AddSourceLogic } from './add_source_logic'; import { - CONNECT_REMOTE, - CONNECT_PRIVATE, CONNECT_WHICH_OPTION_LINK, CONNECT_DOC_PERMISSIONS_LABEL, CONNECT_DOC_PERMISSIONS_TITLE, CONNECT_NEEDS_PERMISSIONS, CONNECT_NOT_SYNCED_TITLE, CONNECT_NOT_SYNCED_TEXT, + SOURCE_FEATURES_DOCUMENT_LEVEL_PERMISSIONS_FEATURE, + SOURCE_FEATURES_DOCUMENT_LEVEL_PERMISSIONS_TITLE, + SOURCE_FEATURES_EXPLORE_BUTTON, } from './constants'; import { SourceFeatures } from './source_features'; @@ -110,6 +111,10 @@ export const ConnectInstance: React.FC = ({ onSubmit(); }; + const permissionsExcluded = features?.basicOrgContextExcludedFeatures?.includes( + FeatureIds.DocumentLevelPermissions + ); + const credentialsFields = ( <> @@ -147,35 +152,6 @@ export const ConnectInstance: React.FC = ({ ); - const featureBadgeGroup = () => { - if (isOrganization) { - return null; - } - - const isRemote = features?.platinumPrivateContext.includes(FeatureIds.Remote); - const isPrivate = features?.platinumPrivateContext.includes(FeatureIds.Private); - - if (isRemote || isPrivate) { - return ( - <> - - {isRemote && {CONNECT_REMOTE}} - {isPrivate && {CONNECT_PRIVATE}} - - - - ); - } - }; - - const descriptionBlock = ( - - {sourceDescription &&

{sourceDescription}

} - {connectStepDescription &&

{connectStepDescription}

} - -
- ); - const whichDocsLink = ( {CONNECT_WHICH_OPTION_LINK} @@ -184,54 +160,87 @@ export const ConnectInstance: React.FC = ({ const permissionField = ( <> - - - {CONNECT_DOC_PERMISSIONS_TITLE} - - - - {CONNECT_DOC_PERMISSIONS_LABEL}} - name="index_permissions" - onChange={(e) => setSourceIndexPermissionsValue(e.target.checked)} - checked={indexPermissionsValue} - disabled={!needsPermissions} - /> - - - {!needsPermissions && ( - - - {LEARN_MORE_LINK} - - ), - }} - /> - - )} - {needsPermissions && indexPermissionsValue && ( - - {CONNECT_NEEDS_PERMISSIONS} -
- {whichDocsLink} -
+ + +

+ {CONNECT_DOC_PERMISSIONS_TITLE} +

+
+ + + {!needsPermissions && ( + + + {LEARN_MORE_LINK} + + ), + }} + /> + + )} + {needsPermissions && indexPermissionsValue && ( + + {CONNECT_NEEDS_PERMISSIONS} + + {whichDocsLink} + + )} + + + {!indexPermissionsValue && ( + <> + + +

+ {CONNECT_NOT_SYNCED_TEXT} + {needsPermissions && whichDocsLink} +

+
+ )} -
- - {!indexPermissionsValue && ( - -

- {CONNECT_NOT_SYNCED_TEXT} - {needsPermissions && whichDocsLink} -

-
- )} - + + {CONNECT_DOC_PERMISSIONS_LABEL}} + name="index_permissions" + onChange={(e) => setSourceIndexPermissionsValue(e.target.checked)} + checked={indexPermissionsValue} + disabled={!needsPermissions} + /> + + + + ); + + const documentLevelPermissionsCallout = ( + <> + + + + + + + + {SOURCE_FEATURES_DOCUMENT_LEVEL_PERMISSIONS_TITLE} + + + + + +

{SOURCE_FEATURES_DOCUMENT_LEVEL_PERMISSIONS_FEATURE}

+
+ + + + {SOURCE_FEATURES_EXPLORE_BUTTON} + + +
+ ); @@ -240,6 +249,7 @@ export const ConnectInstance: React.FC = ({ {isOrganization && hasPlatinumLicense && permissionField} {!hasOauthRedirect && credentialsFields} {needsSubdomain && subdomainField} + {permissionsExcluded && !hasPlatinumLicense && documentLevelPermissionsCallout} @@ -262,15 +272,19 @@ export const ConnectInstance: React.FC = ({ gutterSize="xl" responsive={false} > - - {header} - {featureBadgeGroup()} - {descriptionBlock} + + + + {header} + + + + + + + {formFields} - - -
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts index dcede11fbdd3a..dd756a51fded3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts @@ -338,6 +338,13 @@ export const SOURCE_FEATURES_GLOBAL_ACCESS_PERMISSIONS_FEATURE = i18n.translate( } ); +export const SOURCE_FEATURES_DOCUMENT_LEVEL_PERMISSIONS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.title', + { + defaultMessage: 'Document-level permissions available with Platinum license', + } +); + export const SOURCE_FEATURES_DOCUMENT_LEVEL_PERMISSIONS_FEATURE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.text', { @@ -352,28 +359,6 @@ export const SOURCE_FEATURES_EXPLORE_BUTTON = i18n.translate( defaultMessage: 'Explore Platinum features', } ); - -export const SOURCE_FEATURES_INCLUDED_FEATURES_TITLE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.included.title', - { - defaultMessage: 'Included features', - } -); - -export const CONNECT_REMOTE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.connect.remote.text', - { - defaultMessage: 'Remote', - } -); - -export const CONNECT_PRIVATE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.connect.private.text', - { - defaultMessage: 'Private', - } -); - export const CONNECT_WHICH_OPTION_LINK = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.connect.whichOption.link', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx index eb6736d84a197..61682dbb87d58 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx @@ -51,6 +51,7 @@ export const ReAuthenticate: React.FC = ({ name, header }) return (
{header} +
; + export const SourceFeatures: React.FC = ({ features, objTypes, name }) => { const { hasPlatinumLicense } = useValues(LicensingLogic); const { isOrganization } = useValues(AppLogic); - const Feature = ({ title, children }: { title: string; children: React.ReactElement }) => ( - <> - - - {title} - - - {children} - - ); + const Feature = ({ + icon, + title, + children, + }: { + icon: string; + title: string; + children: React.ReactElement; + }) => { + return ( + <> + + {icon && ( + <> + + + + + )} + + + {title} + + + + + {children} + + ); + }; const SyncFrequencyFeature = ( - +

= ({ features, objTy ); const SyncedItemsFeature = ( - + <>

{SOURCE_FEATURES_SEARCHABLE}

@@ -93,7 +110,7 @@ export const SourceFeatures: React.FC = ({ features, objTy ); const SearchableContentFeature = ( - +

{SOURCE_FEATURES_SEARCHABLE}

@@ -109,7 +126,7 @@ export const SourceFeatures: React.FC = ({ features, objTy ); const RemoteFeature = ( - +

{SOURCE_FEATURES_REMOTE_FEATURE}

@@ -117,7 +134,7 @@ export const SourceFeatures: React.FC = ({ features, objTy ); const PrivateFeature = ( - +

{SOURCE_FEATURES_PRIVATE_FEATURE}

@@ -125,25 +142,14 @@ export const SourceFeatures: React.FC = ({ features, objTy ); const GlobalAccessPermissionsFeature = ( - +

{SOURCE_FEATURES_GLOBAL_ACCESS_PERMISSIONS_FEATURE}

); - const DocumentLevelPermissionsFeature = ( - - -

{SOURCE_FEATURES_DOCUMENT_LEVEL_PERMISSIONS_FEATURE}

- - {SOURCE_FEATURES_EXPLORE_BUTTON} - -
-
- ); - - const FeaturesRouter = ({ featureId }: { featureId: FeatureIds }) => + const FeaturesRouter = ({ featureId }: { featureId: IncludedFeatureIds }) => ({ [FeatureIds.SyncFrequency]: SyncFrequencyFeature, [FeatureIds.SearchableContent]: SearchableContentFeature, @@ -151,10 +157,9 @@ export const SourceFeatures: React.FC = ({ features, objTy [FeatureIds.Remote]: RemoteFeature, [FeatureIds.Private]: PrivateFeature, [FeatureIds.GlobalAccessPermissions]: GlobalAccessPermissionsFeature, - [FeatureIds.DocumentLevelPermissions]: DocumentLevelPermissionsFeature, }[featureId]); - const IncludedFeatures = () => { + const IncludedFeatureIds = () => { let includedFeatures: FeatureIds[] | undefined; if (!hasPlatinumLicense && isOrganization) { @@ -172,55 +177,40 @@ export const SourceFeatures: React.FC = ({ features, objTy } return ( - + <> -

{SOURCE_FEATURES_INCLUDED_FEATURES_TITLE}

+

+ Included features +

- {includedFeatures.map((featureId, i) => ( - - ))} -
- ); - }; - - const ExcludedFeatures = () => { - let excludedFeatures: FeatureIds[] | undefined; - - if (!hasPlatinumLicense && isOrganization) { - excludedFeatures = features?.basicOrgContextExcludedFeatures; - } - - if (!excludedFeatures?.length) { - return null; - } - - return ( - - - {excludedFeatures.map((featureId, i) => ( - - ))} - + + + {includedFeatures.map((featureId, i) => ( + + + + + + ))} + + ); }; return ( - - - - - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts index 8efa61c1ed524..3e1290292704e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts @@ -413,10 +413,6 @@ export const SHARED_EMPTY_DESCRIPTION = i18n.translate( } ); -export const AND = i18n.translate('xpack.enterpriseSearch.workplaceSearch.and', { - defaultMessage: 'and', -}); - export const LICENSE_CALLOUT_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.sources.licenseCallout.title', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.test.tsx new file mode 100644 index 0000000000000..e6f19ff13b3cc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.test.tsx @@ -0,0 +1,111 @@ +/* + * 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 '../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues, setMockActions } from '../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiCallOut, EuiEmptyPrompt } from '@elastic/eui'; + +import { Loading } from '../../../shared/loading'; +import { ContentSection } from '../../components/shared/content_section'; +import { SourcesTable } from '../../components/shared/sources_table'; + +import { PrivateSources } from './private_sources'; +import { SourcesView } from './sources_view'; + +describe('PrivateSources', () => { + const mockValues = { + account: { canCreatePersonalSources: false, groups: [] }, + dataLoading: false, + contentSources: [], + privateContentSources: [], + serviceTypes: [], + hasPlatinumLicense: true, + }; + + beforeEach(() => { + setMockActions({ initializeSources: jest.fn() }); + setMockValues({ ...mockValues }); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(SourcesView)).toHaveLength(1); + }); + + it('renders Loading when loading', () => { + setMockValues({ ...mockValues, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('renders only shared sources section when canCreatePersonalSources is false', () => { + setMockValues({ ...mockValues }); + const wrapper = shallow(); + + expect(wrapper.find(ContentSection)).toHaveLength(1); + }); + + it('renders both shared and private sources sections when canCreatePersonalSources is true', () => { + setMockValues({ ...mockValues, account: { canCreatePersonalSources: true, groups: [] } }); + const wrapper = shallow(); + + expect(wrapper.find(ContentSection)).toHaveLength(2); + }); + + it('renders license callout when has private sources with non-Platinum license', () => { + setMockValues({ + ...mockValues, + privateContentSources: ['source1', 'source2'], + hasPlatinumLicense: false, + account: { canCreatePersonalSources: true, groups: [] }, + }); + const wrapper = shallow(); + + expect(wrapper.find(EuiCallOut)).toHaveLength(1); + }); + + it('renders an action button when user can add private sources', () => { + setMockValues({ + ...mockValues, + account: { canCreatePersonalSources: true, groups: [] }, + serviceTypes: [{ configured: true }], + }); + const wrapper = shallow(); + + expect(wrapper.find(ContentSection).first().prop('action')).toBeTruthy(); + }); + + it('renders empty prompts if no sources are available', () => { + setMockValues({ + ...mockValues, + account: { canCreatePersonalSources: true, groups: [] }, + }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(2); + }); + + it('renders SourcesTable if sources are available', () => { + setMockValues({ + ...mockValues, + account: { canCreatePersonalSources: true, groups: [] }, + contentSources: ['1', '2'], + privateContentSources: ['1', '2'], + }); + const wrapper = shallow(); + + expect(wrapper.find(SourcesTable)).toHaveLength(2); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx index 3ff14602b979d..114df3cf41e39 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx @@ -20,9 +20,9 @@ import noSharedSourcesIcon from '../../assets/share_circle.svg'; import { ContentSection } from '../../components/shared/content_section'; import { SourcesTable } from '../../components/shared/sources_table'; import { ADD_SOURCE_PATH, getSourcesPath } from '../../routes'; +import { toSentenceSerial } from '../../utils'; import { - AND, PRIVATE_LINK_TITLE, PRIVATE_HEADER_TITLE, PRIVATE_HEADER_DESCRIPTION, @@ -122,13 +122,6 @@ export const PrivateSources: React.FC = () => { ); - const groupsSentence = - groups.length === 1 - ? `${groups}` - : `${groups.slice(0, groups.length - 1).join(', ')}${ - groups.length === 2 ? '' : ',' - } ${AND} ${groups.slice(-1)}`; - const sharedSourcesSection = ( { }} + values={{ + groups: groups.length, + groupsSentence: toSentenceSerial(groups), + newline:
, + }} /> ) } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources_layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources_layout.test.tsx new file mode 100644 index 0000000000000..488eb4b49853b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources_layout.test.tsx @@ -0,0 +1,69 @@ +/* + * 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 '../../../__mocks__/shallow_useeffect.mock'; + +import { setMockValues } from '../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiCallOut } from '@elastic/eui'; + +import { ViewContentHeader } from '../../components/shared/view_content_header'; + +import { + PRIVATE_CAN_CREATE_PAGE_TITLE, + PRIVATE_VIEW_ONLY_PAGE_TITLE, + PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION, + PRIVATE_CAN_CREATE_PAGE_DESCRIPTION, +} from './constants'; +import { PrivateSourcesLayout } from './private_sources_layout'; + +describe('PrivateSourcesLayout', () => { + const mockValues = { + account: { canCreatePersonalSources: true }, + }; + + const children =

test

; + + beforeEach(() => { + setMockValues({ ...mockValues }); + }); + + it('renders', () => { + const wrapper = shallow({children}); + + expect(wrapper.find('[data-test-subj="TestChildren"]')).toHaveLength(1); + }); + + it('uses correct title and description when private sources are enabled', () => { + const wrapper = shallow({children}); + + expect(wrapper.find(ViewContentHeader).prop('title')).toEqual(PRIVATE_CAN_CREATE_PAGE_TITLE); + expect(wrapper.find(ViewContentHeader).prop('description')).toEqual( + PRIVATE_CAN_CREATE_PAGE_DESCRIPTION + ); + }); + + it('uses correct title and description when private sources are disabled', () => { + setMockValues({ account: { canCreatePersonalSources: false } }); + const wrapper = shallow({children}); + + expect(wrapper.find(ViewContentHeader).prop('title')).toEqual(PRIVATE_VIEW_ONLY_PAGE_TITLE); + expect(wrapper.find(ViewContentHeader).prop('description')).toEqual( + PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION + ); + }); + + it('renders callout when in read-only mode', () => { + const wrapper = shallow({children}); + + expect(wrapper.find(EuiCallOut)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/infra/public/apps/common_providers.tsx b/x-pack/plugins/infra/public/apps/common_providers.tsx index 972c6f8b14f8c..64867c5743d0d 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 766b8076bc93d..e1427bc96e7e0 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 609a29e5842fe..d621cae3e628c 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 d4bb83e8668ba..07afbfdb5d4ed 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({ diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts index 35decbacf2a52..7473907b7410b 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts @@ -8,7 +8,6 @@ import { uniq } from 'lodash'; import { InfraTimerangeInput } from '../../../../common/http_api'; import { ESSearchClient } from '../../../lib/metrics/types'; -import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; import { calculateMetricInterval } from '../../../utils/calculate_metric_interval'; import { getMetricsAggregations, InfraSnapshotRequestOptions } from './get_metrics_aggregations'; import { @@ -19,9 +18,6 @@ import { getDatasetForField } from '../../metrics_explorer/lib/get_dataset_for_f const createInterval = async (client: ESSearchClient, options: InfraSnapshotRequestOptions) => { const { timerange } = options; - if (timerange.forceInterval && timerange.interval) { - return getIntervalInSeconds(timerange.interval); - } const aggregations = getMetricsAggregations(options); const modules = await aggregationsToModules(client, aggregations, options); return Math.max( @@ -44,14 +40,21 @@ export const createTimeRangeWithInterval = async ( options: InfraSnapshotRequestOptions ): Promise => { const { timerange } = options; - const calculatedInterval = await createInterval(client, options); + if (timerange.forceInterval) { + return { + interval: timerange.interval, + from: timerange.from, + to: timerange.to, + }; + } if (timerange.ignoreLookback) { return { - interval: `${calculatedInterval}s`, + interval: 'modules', from: timerange.from, to: timerange.to, }; } + const calculatedInterval = await createInterval(client, options); const lookbackSize = Math.max(timerange.lookbackSize || 5, 5); return { interval: `${calculatedInterval}s`, diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap index 158c7fa4aeec3..992301af13ad0 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap +++ b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap @@ -8,6 +8,11 @@ exports[`DatatableComponent it renders actions column when there are row actions { + const table: Datatable = { + type: 'datatable', + columns: [ + { + id: 'a', + name: 'a', + meta: { + type: 'number', + }, + }, + ], + rows: [{ a: 123 }], + }; + const CellRenderer = createGridCell( + { + a: { convert: (x) => `formatted ${x}` } as FieldFormat, + }, + DataContext + ); + + it('renders formatted value', () => { + const instance = mountWithIntl( + + {}} + isExpandable={false} + isDetails={false} + isExpanded={false} + /> + + ); + expect(instance.text()).toEqual('formatted 123'); + }); + + it('set class with text alignment', () => { + const cell = mountWithIntl( + + {}} + isExpandable={false} + isDetails={false} + isExpanded={false} + /> + + ); + expect(cell.find('.lnsTableCell').prop('className')).toContain('--right'); + }); +}); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx index a6e1e3386bcf3..2261dd06b532b 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx @@ -14,19 +14,21 @@ export const createGridCell = ( formatters: Record>, DataContext: React.Context ) => ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => { - const { table } = useContext(DataContext); + const { table, alignments } = useContext(DataContext); const rowValue = table?.rows[rowIndex][columnId]; const content = formatters[columnId]?.convert(rowValue, 'html'); + const currentAlignment = alignments && alignments[columnId]; + const alignmentClassName = `lnsTableCell--${currentAlignment}`; return ( - ); }; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx new file mode 100644 index 0000000000000..e0d31a3ed0201 --- /dev/null +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx @@ -0,0 +1,112 @@ +/* + * 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 { EuiButtonGroup } from '@elastic/eui'; +import { FramePublicAPI, VisualizationDimensionEditorProps } from '../../types'; +import { DatatableVisualizationState } from '../visualization'; +import { createMockDatasource, createMockFramePublicAPI } from '../../editor_frame_service/mocks'; +import { mountWithIntl } from '@kbn/test/jest'; +import { TableDimensionEditor } from './dimension_editor'; + +describe('data table dimension editor', () => { + let frame: FramePublicAPI; + let state: DatatableVisualizationState; + let setState: (newState: DatatableVisualizationState) => void; + let props: VisualizationDimensionEditorProps; + + function testState(): DatatableVisualizationState { + return { + layerId: 'first', + columns: [ + { + columnId: 'foo', + }, + ], + }; + } + + beforeEach(() => { + state = testState(); + frame = createMockFramePublicAPI(); + frame.datasourceLayers = { + first: createMockDatasource('test').publicAPIMock, + }; + frame.activeData = { + first: { + type: 'datatable', + columns: [ + { + id: 'foo', + name: 'foo', + meta: { + type: 'string', + }, + }, + ], + rows: [], + }, + }; + setState = jest.fn(); + props = { + accessor: 'foo', + frame, + groupId: 'columns', + layerId: 'first', + state, + setState, + }; + }); + + it('should render default alignment', () => { + const instance = mountWithIntl(); + expect(instance.find(EuiButtonGroup).prop('idSelected')).toEqual( + expect.stringContaining('left') + ); + }); + + it('should render default alignment for number', () => { + frame.activeData!.first.columns[0].meta.type = 'number'; + const instance = mountWithIntl(); + expect(instance.find(EuiButtonGroup).prop('idSelected')).toEqual( + expect.stringContaining('right') + ); + }); + + it('should render specific alignment', () => { + state.columns[0].alignment = 'center'; + const instance = mountWithIntl(); + expect(instance.find(EuiButtonGroup).prop('idSelected')).toEqual( + expect.stringContaining('center') + ); + }); + + it('should set state for the right column', () => { + state.columns = [ + { + columnId: 'foo', + }, + { + columnId: 'bar', + }, + ]; + const instance = mountWithIntl(); + instance.find(EuiButtonGroup).prop('onChange')('center'); + expect(setState).toHaveBeenCalledWith({ + ...state, + columns: [ + { + columnId: 'foo', + alignment: 'center', + }, + { + columnId: 'bar', + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx index 008b805bc8fed..9c60cd47af3e3 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx @@ -7,55 +7,121 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSwitch, EuiFormRow } from '@elastic/eui'; +import { EuiFormRow, EuiSwitch, EuiButtonGroup, htmlIdGenerator } from '@elastic/eui'; import { VisualizationDimensionEditorProps } from '../../types'; import { DatatableVisualizationState } from '../visualization'; +const idPrefix = htmlIdGenerator()(); + export function TableDimensionEditor( props: VisualizationDimensionEditorProps ) { - const { state, setState, accessor } = props; - const column = state.columns.find((c) => c.columnId === accessor); + const { state, setState, frame, accessor } = props; + const column = state.columns.find(({ columnId }) => accessor === columnId); - const visibleColumnsCount = state.columns.filter((c) => !c.hidden).length; + if (!column) return null; - if (!column) { - return null; - } + // either read config state or use same logic as chart itself + const currentAlignment = + column?.alignment || + (frame.activeData && + frame.activeData[state.layerId].columns.find((col) => col.id === accessor)?.meta.type === + 'number' + ? 'right' + : 'left'); + + const visibleColumnsCount = state.columns.filter((c) => !c.hidden).length; return ( - + + ); } diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss index 5e5db2c645809..b99ffb6dce810 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.scss @@ -1,3 +1,19 @@ .lnsDataTableContainer { height: 100%; } + +.lnsTableCell { + @include euiTextTruncate; +} + +.lnsTableCell--left { + text-align: left; +} + +.lnsTableCell--right { + text-align: right; +} + +.lnsTableCell--center { + text-align: center; +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx index 588340fbe97fa..22577e8ef5fd3 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx @@ -12,7 +12,7 @@ import { EuiDataGrid } from '@elastic/eui'; import { IAggType, IFieldFormat } from 'src/plugins/data/public'; import { EmptyPlaceholder } from '../../shared_components'; import { LensIconChartDatatable } from '../../assets/chart_datatable'; -import { DatatableComponent } from './table_basic'; +import { DataContext, DatatableComponent } from './table_basic'; import { LensMultiTable } from '../../types'; import { DatatableProps } from '../expression'; @@ -427,6 +427,39 @@ describe('DatatableComponent', () => { expect(wrapper.find(EuiDataGrid).prop('columns')!.length).toEqual(2); }); + test('it adds alignment data to context', () => { + const { data, args } = sampleArgs(); + + const wrapper = shallow( + ({ convert: (x) => x } as IFieldFormat)} + dispatchEvent={onDispatchEvent} + getType={jest.fn()} + renderMode="display" + /> + ); + + expect(wrapper.find(DataContext.Provider).prop('value').alignments).toEqual({ + // set via args + a: 'center', + // default for date + b: 'left', + // default for number + c: 'right', + }); + }); + test('it should refresh the table header when the datatable data changes', () => { const { data, args } = sampleArgs(); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx index f685990f12dd2..e1687ba28f07b 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx @@ -40,7 +40,7 @@ import { createGridSortingConfig, } from './table_actions'; -const DataContext = React.createContext({}); +export const DataContext = React.createContext({}); const gridStyle: EuiDataGridStyle = { border: 'horizontal', @@ -192,6 +192,21 @@ export const DatatableComponent = (props: DatatableRenderProps) => { ] ); + const alignments: Record = useMemo(() => { + const alignmentMap: Record = {}; + columnConfig.columns.forEach((column) => { + if (column.alignment) { + alignmentMap[column.columnId] = column.alignment; + } else { + const isNumeric = + firstLocalTable.columns.find((dataColumn) => dataColumn.id === column.columnId)?.meta + .type === 'number'; + alignmentMap[column.columnId] = isNumeric ? 'right' : 'left'; + } + }); + return alignmentMap; + }, [firstLocalTable, columnConfig]); + const trailingControlColumns: EuiDataGridControlColumn[] = useMemo(() => { if (!hasAtLeastOneRowClickAction || !onRowContextMenuClick) { return []; @@ -259,6 +274,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { value={{ table: firstLocalTable, rowHasRowClickTriggerActions: props.rowHasRowClickTriggerActions, + alignments, }} > ; } diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx index 7ead7be67947c..f6a38541cda27 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx @@ -138,6 +138,7 @@ export const datatableColumn: ExpressionFunctionDefinition< inputTypes: ['null'], args: { columnId: { types: ['string'], help: '' }, + alignment: { types: ['string'], help: '' }, hidden: { types: ['boolean'], help: '' }, width: { types: ['number'], help: '' }, }, diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx index ad5e1e552ccd2..92136c557ad38 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx @@ -419,11 +419,13 @@ describe('Datatable Visualization', () => { columnId: ['c'], hidden: [], width: [], + alignment: [], }); expect(columnArgs[1].arguments).toEqual({ columnId: ['b'], hidden: [], width: [], + alignment: [], }); }); diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx index 47f8ce09aea68..fc69c914deb68 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx @@ -23,6 +23,7 @@ export interface ColumnState { columnId: string; width?: number; hidden?: boolean; + alignment?: 'left' | 'right' | 'center'; } export interface SortingState { @@ -264,6 +265,7 @@ export const datatableVisualization: Visualization columnId: [column.columnId], hidden: typeof column.hidden === 'undefined' ? [] : [column.hidden], width: typeof column.width === 'undefined' ? [] : [column.width], + alignment: typeof column.alignment === 'undefined' ? [] : [column.alignment], }, }, ], diff --git a/x-pack/plugins/lens/public/drag_drop/announcements.tsx b/x-pack/plugins/lens/public/drag_drop/announcements.tsx index 61785310bdcf3..3c65008f8f38b 100644 --- a/x-pack/plugins/lens/public/drag_drop/announcements.tsx +++ b/x-pack/plugins/lens/public/drag_drop/announcements.tsx @@ -11,6 +11,7 @@ export interface HumanData { label: string; groupLabel?: string; position?: number; + nextLabel?: string; } type AnnouncementFunction = (draggedElement: HumanData, dropElement: HumanData) => string; @@ -25,7 +26,7 @@ const selectedTargetReplace = ( { label: dropLabel, groupLabel, position }: HumanData ) => i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.replace', { - defaultMessage: `Selected {dropLabel} in {groupLabel} group at position {position}. Press space or enter to replace {dropLabel} with {label}.`, + defaultMessage: `Replace {dropLabel} in {groupLabel} group at position {position} with {label}. Press space or enter to replace`, values: { label, dropLabel, @@ -34,65 +35,103 @@ const selectedTargetReplace = ( }, }); -const droppedReplace = ({ label }: HumanData, { label: dropLabel, groupLabel }: HumanData) => +const droppedReplace = ( + { label }: HumanData, + { label: dropLabel, groupLabel, position }: HumanData +) => i18n.translate('xpack.lens.dragDrop.announce.duplicated.replace', { - defaultMessage: - 'You have dropped the item. You have replaced {dropLabel} with {label} in {groupLabel} group.', + defaultMessage: 'Replaced {dropLabel} with {label} in {groupLabel} at position {position}', values: { label, dropLabel, groupLabel, + position, }, }); export const announcements: CustomAnnouncementsType = { selectedTarget: { - reorder: ({ label, position: prevPosition }, { position }) => + reorder: ({ label, groupLabel, position: prevPosition }, { position }) => prevPosition === position ? i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.reorderedBack', { - defaultMessage: `You have moved the item {label} back to position {prevPosition}`, + defaultMessage: `{label} returned to its initial position {prevPosition}`, values: { label, prevPosition, }, }) : i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.reordered', { - defaultMessage: `You have moved the item {label} from position {prevPosition} to position {position}`, + defaultMessage: `Reorder {label} in {groupLabel} group from position {prevPosition} to position {position}. Press space or enter to reorder`, values: { + groupLabel, label, position, prevPosition, }, }), - duplicate_in_group: ({ label }, { label: dropLabel, groupLabel, position }) => + duplicate_in_group: ({ label }, { groupLabel, position }) => i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.duplicated', { - defaultMessage: `Selected {dropLabel} in {groupLabel} group at position {position}. Press space or enter to duplicate {label}.`, + defaultMessage: `Duplicate {label} to {groupLabel} group at position {position}. Press space or enter to duplicate`, values: { label, - dropLabel, groupLabel, position, }, }), field_replace: selectedTargetReplace, replace_compatible: selectedTargetReplace, - replace_incompatible: selectedTargetReplace, + replace_incompatible: ( + { label }: HumanData, + { label: dropLabel, groupLabel, position, nextLabel }: HumanData + ) => + i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.replaceIncompatible', { + defaultMessage: `Convert {label} to {nextLabel} and replace {dropLabel} in {groupLabel} group at position {position}. Press space or enter to replace`, + values: { + label, + nextLabel, + dropLabel, + groupLabel, + position, + }, + }), + move_incompatible: ( + { label }: HumanData, + { label: groupLabel, position, nextLabel }: HumanData + ) => + i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.moveIncompatible', { + defaultMessage: `Convert {label} to {nextLabel} and move to {groupLabel} group at position {position}. Press space or enter to move`, + values: { + label, + nextLabel, + groupLabel, + position, + }, + }), + move_compatible: ({ label }: HumanData, { groupLabel, position }: HumanData) => + i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.moveCompatible', { + defaultMessage: `Move {label} to {groupLabel} group at position {position}. Press space or enter to move`, + values: { + label, + groupLabel, + position, + }, + }), }, dropped: { - reorder: ({ label, position: prevPosition }, { position }) => + reorder: ({ label, groupLabel, position: prevPosition }, { position }) => i18n.translate('xpack.lens.dragDrop.announce.dropped.reordered', { defaultMessage: - 'You have dropped the item {label}. You have moved the item from position {prevPosition} to positon {position}', + 'Reordered {label} in {groupLabel} group from position {prevPosition} to positon {position}', values: { label, + groupLabel, position, prevPosition, }, }), duplicate_in_group: ({ label }, { groupLabel, position }) => i18n.translate('xpack.lens.dragDrop.announce.dropped.duplicated', { - defaultMessage: - 'You have dropped the item. You have duplicated {label} in {groupLabel} group at position {position}', + defaultMessage: 'Duplicated {label} in {groupLabel} group at position {position}', values: { label, groupLabel, @@ -101,7 +140,42 @@ export const announcements: CustomAnnouncementsType = { }), field_replace: droppedReplace, replace_compatible: droppedReplace, - replace_incompatible: droppedReplace, + replace_incompatible: ( + { label }: HumanData, + { label: dropLabel, groupLabel, position, nextLabel }: HumanData + ) => + i18n.translate('xpack.lens.dragDrop.announce.dropped.replaceIncompatible', { + defaultMessage: + 'Converted {label} to {nextLabel} and replaced {dropLabel} in {groupLabel} group at position {position}', + values: { + label, + nextLabel, + dropLabel, + groupLabel, + position, + }, + }), + move_incompatible: ({ label }: HumanData, { groupLabel, position, nextLabel }: HumanData) => + i18n.translate('xpack.lens.dragDrop.announce.dropped.moveIncompatible', { + defaultMessage: + 'Converted {label} to {nextLabel} and moved to {groupLabel} group at position {position}', + values: { + label, + nextLabel, + groupLabel, + position, + }, + }), + + move_compatible: ({ label }: HumanData, { groupLabel, position }: HumanData) => + i18n.translate('xpack.lens.dragDrop.announce.dropped.moveCompatible', { + defaultMessage: 'Moved {label} to {groupLabel} group at position {position}', + values: { + label, + groupLabel, + position, + }, + }), }, }; @@ -113,13 +187,29 @@ const defaultAnnouncements = { label, }, }), - cancelled: () => - i18n.translate('xpack.lens.dragDrop.announce.cancelled', { - defaultMessage: 'Movement cancelled', - }), + cancelled: ({ label, groupLabel, position }: HumanData) => { + if (!groupLabel || !position) { + return i18n.translate('xpack.lens.dragDrop.announce.cancelled', { + defaultMessage: 'Movement cancelled. {label} returned to its initial position', + values: { + label, + }, + }); + } + return i18n.translate('xpack.lens.dragDrop.announce.cancelledItem', { + defaultMessage: + 'Movement cancelled. {label} returned to {groupLabel} group at position {position}', + values: { + label, + groupLabel, + position, + }, + }); + }, + noTarget: () => { return i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.noSelected', { - defaultMessage: `No target selected. Use arrow keys to select a target.`, + defaultMessage: `No target selected. Use arrow keys to select a target`, }); }, @@ -129,17 +219,15 @@ const defaultAnnouncements = { ) => dropGroupLabel && position ? i18n.translate('xpack.lens.dragDrop.announce.droppedDefault', { - defaultMessage: - 'You have dropped {label} to {dropLabel} in {dropGroupLabel} group at position {position}', + defaultMessage: 'Added {label} in {dropGroupLabel} group at position {position}', values: { label, dropGroupLabel, position, - dropLabel, }, }) : i18n.translate('xpack.lens.dragDrop.announce.droppedNoPosition', { - defaultMessage: 'You have dropped {label} to {dropLabel}', + defaultMessage: 'Added {label} to {dropLabel}', values: { label, dropLabel, @@ -151,16 +239,15 @@ const defaultAnnouncements = { ) => { return dropGroupLabel && position ? i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.default', { - defaultMessage: `Selected {dropLabel} in {dropGroupLabel} group at position {position}. Press space or enter to drop {label}`, + defaultMessage: `Add {label} to {dropGroupLabel} group at position {position}. Press space or enter to add`, values: { - dropLabel, label, dropGroupLabel, position, }, }) : i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.defaultNoPosition', { - defaultMessage: `Selected {dropLabel}. Press space or enter to drop {label}`, + defaultMessage: `Add {label} to {dropLabel}. Press space or enter to add`, values: { dropLabel, label, diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx index f9bae547a563b..f2a2fda730388 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx @@ -110,7 +110,7 @@ describe('DragDrop', () => { const component = mount( @@ -126,7 +126,7 @@ describe('DragDrop', () => { expect(preventDefault).toBeCalled(); expect(stopPropagation).toBeCalled(); expect(setDragging).toBeCalledWith(undefined); - expect(onDrop).toBeCalledWith({ id: '2', humanData: { label: 'label1' } }, 'field_add'); + expect(onDrop).toBeCalledWith({ id: '2', humanData: { label: 'Label1' } }, 'field_add'); }); test('drop function is not called on dropType undefined', async () => { @@ -138,7 +138,7 @@ describe('DragDrop', () => { const component = mount( @@ -195,7 +195,7 @@ describe('DragDrop', () => { }); test('additional styles are reflected in the className until drop', () => { - let dragging: { id: '1'; humanData: { label: 'label1' } } | undefined; + let dragging: { id: '1'; humanData: { label: 'Label1' } } | undefined; const getAdditionalClassesOnEnter = jest.fn().mockReturnValue('additional'); const getAdditionalClassesOnDroppable = jest.fn().mockReturnValue('droppable'); const setA11yMessage = jest.fn(); @@ -206,7 +206,7 @@ describe('DragDrop', () => { dragging={dragging} setA11yMessage={setA11yMessage} setDragging={() => { - dragging = { id: '1', humanData: { label: 'label1' } }; + dragging = { id: '1', humanData: { label: 'Label1' } }; }} > { }); test('additional enter styles are reflected in the className until dragleave', () => { - let dragging: { id: '1'; humanData: { label: 'label1' } } | undefined; + let dragging: { id: '1'; humanData: { label: 'Label1' } } | undefined; const getAdditionalClasses = jest.fn().mockReturnValue('additional'); const getAdditionalClassesOnDroppable = jest.fn().mockReturnValue('droppable'); const setActiveDropTarget = jest.fn(); @@ -252,7 +252,7 @@ describe('DragDrop', () => { setA11yMessage={jest.fn()} dragging={dragging} setDragging={() => { - dragging = { id: '1', humanData: { label: 'label1' } }; + dragging = { id: '1', humanData: { label: 'Label1' } }; }} setActiveDropTarget={setActiveDropTarget} activeDropTarget={ @@ -303,7 +303,7 @@ describe('DragDrop', () => { draggable: true, value: { id: '1', - humanData: { label: 'label1', position: 1 }, + humanData: { label: 'Label1', position: 1 }, }, children: '1', order: [2, 0, 0, 0], @@ -326,7 +326,7 @@ describe('DragDrop', () => { dragType: 'move' as 'copy' | 'move', value: { id: '3', - humanData: { label: 'label3', position: 1 }, + humanData: { label: 'label3', position: 1, groupLabel: 'Y' }, }, onDrop, dropType: 'replace_compatible' as DropType, @@ -337,7 +337,7 @@ describe('DragDrop', () => { dragType: 'move' as 'copy' | 'move', value: { id: '4', - humanData: { label: 'label4', position: 2 }, + humanData: { label: 'label4', position: 2, groupLabel: 'Y' }, }, order: [2, 0, 2, 1], }, @@ -380,11 +380,11 @@ describe('DragDrop', () => { }); keyboardHandler.simulate('keydown', { key: 'Enter' }); expect(setA11yMessage).toBeCalledWith( - 'Selected label3 in group at position 1. Press space or enter to replace label3 with label1.' + 'Replace label3 in Y group at position 1 with Label1. Press space or enter to replace' ); expect(setActiveDropTarget).toBeCalledWith(undefined); expect(onDrop).toBeCalledWith( - { humanData: { label: 'label1', position: 1 }, id: '1' }, + { humanData: { label: 'Label1', position: 1 }, id: '1' }, 'move_compatible' ); }); @@ -437,7 +437,7 @@ describe('DragDrop', () => { draggable: true, value: { id: '1', - humanData: { label: 'label1', position: 1 }, + humanData: { label: 'Label1', position: 1 }, }, children: '1', order: [2, 0, 0, 0], @@ -488,19 +488,19 @@ describe('DragDrop', () => { const items = [ { id: '1', - humanData: { label: 'label1', position: 1 }, + humanData: { label: 'Label1', position: 1, groupLabel: 'X' }, onDrop, dropType: 'reorder' as DropType, }, { id: '2', - humanData: { label: 'label2', position: 2 }, + humanData: { label: 'label2', position: 2, groupLabel: 'X' }, onDrop, dropType: 'reorder' as DropType, }, { id: '3', - humanData: { label: 'label3', position: 3 }, + humanData: { label: 'label3', position: 3, groupLabel: 'X' }, onDrop, dropType: 'reorder' as DropType, }, @@ -583,7 +583,7 @@ describe('DragDrop', () => { }); expect(setDragging).toBeCalledWith({ ...items[0] }); - expect(setA11yMessage).toBeCalledWith('Lifted label1'); + expect(setA11yMessage).toBeCalledWith('Lifted Label1'); expect( component .find('[data-test-subj="lnsDragDrop-reorderableGroup"]') @@ -652,7 +652,7 @@ describe('DragDrop', () => { jest.runAllTimers(); expect(setA11yMessage).toBeCalledWith( - 'You have dropped the item label1. You have moved the item from position 1 to positon 3' + 'Reordered Label1 in X group from position 1 to positon 3' ); expect(preventDefault).toBeCalled(); expect(stopPropagation).toBeCalled(); @@ -687,7 +687,7 @@ describe('DragDrop', () => { expect(setActiveDropTarget).toBeCalledWith(items[1]); expect(setA11yMessage).toBeCalledWith( - 'You have moved the item label1 from position 1 to position 2' + 'Reorder Label1 in X group from position 1 to position 2. Press space or enter to reorder' ); }); test(`Keyboard navigation: user can drop element to an activeDropTarget`, () => { @@ -729,13 +729,17 @@ describe('DragDrop', () => { jest.runAllTimers(); expect(onDropHandler).not.toHaveBeenCalled(); - expect(setA11yMessage).toBeCalledWith('Movement cancelled'); + expect(setA11yMessage).toBeCalledWith( + 'Movement cancelled. Label1 returned to X group at position 1' + ); keyboardHandler.simulate('keydown', { key: 'Space' }); keyboardHandler.simulate('keydown', { key: 'ArrowDown' }); keyboardHandler.simulate('blur'); expect(onDropHandler).not.toHaveBeenCalled(); - expect(setA11yMessage).toBeCalledWith('Movement cancelled'); + expect(setA11yMessage).toBeCalledWith( + 'Movement cancelled. Label1 returned to X group at position 1' + ); }); test(`Keyboard Navigation: Reordered elements get extra styles to show the reorder effect`, () => { @@ -772,7 +776,7 @@ describe('DragDrop', () => { component.find('[data-test-subj="lnsDragDrop-translatableDrop"]').at(1).prop('style') ).toEqual(undefined); expect(setA11yMessage).toBeCalledWith( - 'You have moved the item label1 from position 1 to position 2' + 'Reorder Label1 in X group from position 1 to position 2. Press space or enter to reorder' ); component @@ -837,7 +841,7 @@ describe('DragDrop', () => { keyboardHandler.simulate('keydown', { key: 'Space' }); keyboardHandler.simulate('keydown', { key: 'ArrowUp' }); expect(setActiveDropTarget).toBeCalledWith(undefined); - expect(setA11yMessage).toBeCalledWith('You have moved the item label1 back to position 1'); + expect(setA11yMessage).toBeCalledWith('Label1 returned to its initial position 1'); }); }); }); diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx index 4b25064320327..6c6a65ab421b3 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx @@ -267,7 +267,7 @@ const DragInner = memo(function DragInner({ setDragging(undefined); setActiveDropTarget(undefined); setKeyboardMode(false); - setA11yMessage(announce.cancelled()); + setA11yMessage(announce.cancelled(value.humanData)); if (onDragEnd) { onDragEnd(); } diff --git a/x-pack/plugins/lens/public/drag_drop/providers.tsx b/x-pack/plugins/lens/public/drag_drop/providers.tsx index 1a056902a474d..deb9bf6cb17ae 100644 --- a/x-pack/plugins/lens/public/drag_drop/providers.tsx +++ b/x-pack/plugins/lens/public/drag_drop/providers.tsx @@ -204,12 +204,12 @@ export function RootDragDropProvider({ children }: { children: React.ReactNode }

{i18n.translate('xpack.lens.dragDrop.keyboardInstructionsReorder', { - defaultMessage: `Press enter or space to dragging. When dragging, use the up/down arrow keys to reorder items in the group and left/right arrow keys to choose drop targets outside of the group. Press enter or space again to finish.`, + defaultMessage: `Press space or enter to start dragging. When dragging, use the up/down arrow keys to reorder items in the group and left/right arrow keys to choose drop targets outside of the group. Press space or enter again to finish.`, })}

{i18n.translate('xpack.lens.dragDrop.keyboardInstructions', { - defaultMessage: `Press enter or space to start dragging. When dragging, use the left/right arrow keys to move between drop targets. Press enter or space again to finish.`, + defaultMessage: `Press space or enter to start dragging. When dragging, use the left/right arrow keys to move between drop targets. Press space or enter again to finish.`, })}

diff --git a/x-pack/plugins/lens/public/drag_drop/readme.md b/x-pack/plugins/lens/public/drag_drop/readme.md index 55a9e3157c247..01cc4c7bc85a5 100644 --- a/x-pack/plugins/lens/public/drag_drop/readme.md +++ b/x-pack/plugins/lens/public/drag_drop/readme.md @@ -56,7 +56,7 @@ const { dragging } = useContext(DragContext); return ( onChange([...items, item])} > {items.map((x) => ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx index f493000aa587a..1cbd41fff2a8f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx @@ -64,13 +64,16 @@ export function DraggableDimensionButton({ columnId: string; registerNewButtonRef: (id: string, instance: HTMLDivElement | null) => void; }) { - const dropType = layerDatasource.getDropTypes({ + const dropProps = layerDatasource.getDropProps({ ...layerDatasourceDropProps, columnId, filterOperations: group.filterOperations, groupId: group.groupId, }); + const dropType = dropProps?.dropType; + const nextLabel = dropProps?.nextLabel; + const value = useMemo( () => ({ columnId, @@ -82,9 +85,10 @@ export function DraggableDimensionButton({ label, groupLabel: group.groupLabel, position: accessorIndex + 1, + nextLabel: nextLabel || '', }, }), - [columnId, group.groupId, accessorIndex, layerId, dropType, label, group.groupLabel] + [columnId, group.groupId, accessorIndex, layerId, dropType, label, group.groupLabel, nextLabel] ); // todo: simplify by id and use drop targets? diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx index a83d4bde0383c..c9d0a7b002870 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx @@ -54,13 +54,16 @@ export function EmptyDimensionButton({ setNewColumnId(generateId()); }, [itemIndex]); - const dropType = layerDatasource.getDropTypes({ + const dropProps = layerDatasource.getDropProps({ ...layerDatasourceDropProps, columnId: newColumnId, filterOperations: group.filterOperations, groupId: group.groupId, }); + const dropType = dropProps?.dropType; + const nextLabel = dropProps?.nextLabel; + const value = useMemo( () => ({ columnId: newColumnId, @@ -72,9 +75,10 @@ export function EmptyDimensionButton({ label, groupLabel: group.groupLabel, position: itemIndex + 1, + nextLabel: nextLabel || '', }, }), - [dropType, newColumnId, group.groupId, layerId, group.groupLabel, itemIndex] + [dropType, newColumnId, group.groupId, layerId, group.groupLabel, itemIndex, nextLabel] ); return ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 1f97399fdd292..619147987cdd5 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -440,7 +440,10 @@ describe('LayerPanel', () => { ], }); - mockDatasource.getDropTypes.mockReturnValue('field_add'); + mockDatasource.getDropProps.mockReturnValue({ + dropType: 'field_add', + nextLabel: '', + }); const draggingField = { field: { name: 'dragged' }, @@ -459,7 +462,7 @@ describe('LayerPanel', () => { ); - expect(mockDatasource.getDropTypes).toHaveBeenCalledWith( + expect(mockDatasource.getDropProps).toHaveBeenCalledWith( expect.objectContaining({ dragDropContext: expect.objectContaining({ dragging: draggingField, @@ -492,8 +495,8 @@ describe('LayerPanel', () => { ], }); - mockDatasource.getDropTypes.mockImplementation(({ columnId }) => - columnId !== 'a' ? 'field_replace' : undefined + mockDatasource.getDropProps.mockImplementation(({ columnId }) => + columnId !== 'a' ? { dropType: 'field_replace', nextLabel: '' } : undefined ); const draggingField = { @@ -513,7 +516,7 @@ describe('LayerPanel', () => { ); - expect(mockDatasource.getDropTypes).toHaveBeenCalledWith( + expect(mockDatasource.getDropProps).toHaveBeenCalledWith( expect.objectContaining({ columnId: 'a' }) ); @@ -554,7 +557,10 @@ describe('LayerPanel', () => { ], }); - mockDatasource.getDropTypes.mockReturnValue('replace_compatible'); + mockDatasource.getDropProps.mockReturnValue({ + dropType: 'replace_compatible', + nextLabel: '', + }); const draggingOperation = { layerId: 'first', @@ -574,7 +580,7 @@ describe('LayerPanel', () => { ); - expect(mockDatasource.getDropTypes).toHaveBeenCalledWith( + expect(mockDatasource.getDropProps).toHaveBeenCalledWith( expect.objectContaining({ dragDropContext: expect.objectContaining({ dragging: draggingOperation, diff --git a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx index 36c2d7128460c..db3b29bb74d31 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx @@ -88,7 +88,7 @@ export function createMockDatasource(id: string): DatasourceMock { uniqueLabels: jest.fn((_state) => ({})), renderDimensionTrigger: jest.fn(), renderDimensionEditor: jest.fn(), - getDropTypes: jest.fn(), + getDropProps: jest.fn(), onDrop: jest.fn(), // this is an additional property which doesn't exist on real datasources diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index c26d35c4d9a5d..5eaa798f459e3 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -8,7 +8,14 @@ import { ReactWrapper, ShallowWrapper } from 'enzyme'; import React, { ChangeEvent, MouseEvent } from 'react'; import { act } from 'react-dom/test-utils'; -import { EuiComboBox, EuiListGroupItemProps, EuiListGroup, EuiRange } from '@elastic/eui'; +import { + EuiComboBox, + EuiListGroupItemProps, + EuiListGroup, + EuiRange, + EuiSelect, + EuiButtonIcon, +} from '@elastic/eui'; import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { IndexPatternDimensionEditorComponent, @@ -24,8 +31,6 @@ import { OperationMetadata } from '../../types'; import { DateHistogramIndexPatternColumn } from '../operations/definitions/date_histogram'; import { getFieldByNameFactory } from '../pure_helpers'; import { TimeScaling } from './time_scaling'; -import { EuiSelect } from '@elastic/eui'; -import { EuiButtonIcon } from '@elastic/eui'; import { DimensionEditor } from './dimension_editor'; jest.mock('../loader'); @@ -742,6 +747,8 @@ describe('IndexPatternDimensionEditorPanel', () => { }); it('should leave error state when switching from incomplete state to fieldless operation', () => { + // @ts-expect-error + window['__react-beautiful-dnd-disable-dev-warnings'] = true; // issue with enzyme & react-beautiful-dnd throwing errors: https://github.com/atlassian/react-beautiful-dnd/issues/1593 wrapper = mount(); wrapper diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts index 1f0381d92ce64..17f069b8831e7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts @@ -6,7 +6,7 @@ */ import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { IndexPatternDimensionEditorProps } from './dimension_panel'; -import { onDrop, getDropTypes } from './droppable'; +import { onDrop, getDropProps } from './droppable'; import { DragContextState } from '../../drag_drop'; import { createMockedDragDropContext } from '../mocks'; import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup, CoreSetup } from 'kibana/public'; @@ -91,7 +91,7 @@ const draggingField = { * - Dimension trigger: Not tested here * - Dimension editor component: First half of the tests * - * - getDropTypes: Returns drop types that are possible for the current dragging field or other dimension + * - getDropProps: Returns drop types that are possible for the current dragging field or other dimension * - onDrop: Correct application of drop logic */ describe('IndexPatternDimensionEditorPanel', () => { @@ -174,14 +174,14 @@ describe('IndexPatternDimensionEditorPanel', () => { }); const groupId = 'a'; - describe('getDropTypes', () => { + describe('getDropProps', () => { it('returns undefined if no drag is happening', () => { - expect(getDropTypes({ ...defaultProps, groupId, dragDropContext })).toBe(undefined); + expect(getDropProps({ ...defaultProps, groupId, dragDropContext })).toBe(undefined); }); it('returns undefined if the dragged item has no field', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -198,7 +198,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns undefined if field is not supported by filterOperations', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -217,7 +217,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns remove_add if the field is supported by filterOperations and the dropTarget is an existing column', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -226,12 +226,12 @@ describe('IndexPatternDimensionEditorPanel', () => { }, filterOperations: (op: OperationMetadata) => op.dataType === 'number', }) - ).toBe('field_replace'); + ).toEqual({ dropType: 'field_replace', nextLabel: 'Intervals' }); }); it('returns undefined if the field belongs to another index pattern', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -250,7 +250,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns undefined if the dragged field is already in use by this operation', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -275,7 +275,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns move if the dragged column is compatible', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -290,7 +290,7 @@ describe('IndexPatternDimensionEditorPanel', () => { }, columnId: 'col2', }) - ).toBe('move_compatible'); + ).toEqual({ dropType: 'move_compatible' }); }); it('returns undefined if the dragged column from different group uses the same field as the dropTarget', () => { @@ -318,7 +318,7 @@ describe('IndexPatternDimensionEditorPanel', () => { }; expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -357,7 +357,7 @@ describe('IndexPatternDimensionEditorPanel', () => { }; expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -373,7 +373,7 @@ describe('IndexPatternDimensionEditorPanel', () => { columnId: 'col2', filterOperations: (op: OperationMetadata) => op.isBucketed === false, }) - ).toEqual('replace_incompatible'); + ).toEqual({ dropType: 'replace_incompatible', nextLabel: 'Unique count' }); }); }); describe('onDrop', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts index 69c7e8c3c2ae6..be791b3c7f7ce 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts @@ -10,21 +10,29 @@ import { DatasourceDimensionDropHandlerProps, isDraggedOperation, DraggedOperation, + DropType, } from '../../types'; import { IndexPatternColumn } from '../indexpattern'; -import { insertOrReplaceColumn, deleteColumn, getOperationTypesForField } from '../operations'; +import { + insertOrReplaceColumn, + deleteColumn, + getOperationTypesForField, + getOperationDisplay, +} from '../operations'; import { mergeLayer } from '../state_helpers'; import { hasField, isDraggedField } from '../utils'; -import { IndexPatternPrivateState, IndexPatternField, DraggedField } from '../types'; +import { IndexPatternPrivateState, DraggedField } from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; type DropHandlerProps = DatasourceDimensionDropHandlerProps & { droppedItem: T; }; -export function getDropTypes( +const operationLabels = getOperationDisplay(); + +export function getDropProps( props: DatasourceDimensionDropProps & { groupId: string } -) { +): { dropType: DropType; nextLabel?: string } | undefined { const { dragging } = props.dragDropContext; if (!dragging) { return; @@ -32,23 +40,27 @@ export function getDropTypes( const layerIndexPatternId = props.state.layers[props.layerId].indexPatternId; - function hasOperationForField(field: IndexPatternField) { - const operationsForNewField = getOperationTypesForField(field, props.filterOperations); - return !!operationsForNewField.length; - } - const currentColumn = props.state.layers[props.layerId].columns[props.columnId]; if (isDraggedField(dragging)) { - if ( - !!(layerIndexPatternId === dragging.indexPatternId && hasOperationForField(dragging.field)) - ) { + const operationsForNewField = getOperationTypesForField(dragging.field, props.filterOperations); + + if (!!(layerIndexPatternId === dragging.indexPatternId && operationsForNewField.length)) { + const highestPriorityOperationLabel = operationLabels[operationsForNewField[0]].displayName; if (!currentColumn) { - return 'field_add'; + return { dropType: 'field_add', nextLabel: highestPriorityOperationLabel }; } else if ( (hasField(currentColumn) && currentColumn.sourceField !== dragging.field.name) || !hasField(currentColumn) ) { - return 'field_replace'; + const persistingOperationLabel = + currentColumn && + operationsForNewField.includes(currentColumn.operationType) && + operationLabels[currentColumn.operationType].displayName; + + return { + dropType: 'field_replace', + nextLabel: persistingOperationLabel || highestPriorityOperationLabel, + }; } } return; @@ -62,9 +74,9 @@ export function getDropTypes( // same group if (props.groupId === dragging.groupId) { if (currentColumn) { - return 'reorder'; + return { dropType: 'reorder' }; } - return 'duplicate_in_group'; + return { dropType: 'duplicate_in_group' }; } // compatible group @@ -80,20 +92,34 @@ export function getDropTypes( } if (props.filterOperations(op)) { if (currentColumn) { - return 'replace_compatible'; // in the future also 'swap_compatible' and 'duplicate_compatible' + return { dropType: 'replace_compatible' }; // in the future also 'swap_compatible' and 'duplicate_compatible' } else { - return 'move_compatible'; // in the future also 'duplicate_compatible' + return { dropType: 'move_compatible' }; // in the future also 'duplicate_compatible' } } // suggest const field = hasField(op) && props.state.indexPatterns[layerIndexPatternId].getFieldByName(op.sourceField); - if (field && hasOperationForField(field)) { + const operationsForNewField = field && getOperationTypesForField(field, props.filterOperations); + + if (operationsForNewField && operationsForNewField?.length) { + const highestPriorityOperationLabel = operationLabels[operationsForNewField[0]].displayName; + if (currentColumn) { - return 'replace_incompatible'; // in the future also 'swap_incompatible', 'duplicate_incompatible' + const persistingOperationLabel = + currentColumn && + operationsForNewField.includes(currentColumn.operationType) && + operationLabels[currentColumn.operationType].displayName; + return { + dropType: 'replace_incompatible', + nextLabel: persistingOperationLabel || highestPriorityOperationLabel, + }; // in the future also 'swap_incompatible', 'duplicate_incompatible' } else { - return 'move_incompatible'; // in the future also 'duplicate_incompatible' + return { + dropType: 'move_incompatible', + nextLabel: highestPriorityOperationLabel, + }; // in the future also 'duplicate_incompatible' } } } @@ -178,6 +204,12 @@ function onMoveDropToNonCompatibleGroup(props: DropHandlerProps { + const original = jest.requireActual('@elastic/eui'); + + return { + ...original, + htmlIdGenerator: (fn: unknown) => { + let counter = 0; + return () => counter++; + }, + }; +}); + const dataPluginMockValue = dataPluginMock.createStartContract(); // need to overwrite the formatter field first dataPluginMockValue.fieldFormats.deserialize = jest.fn().mockImplementation(({ params }) => { diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 19ce943335a67..419354117eda2 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -189,9 +189,9 @@ export interface Datasource { renderDimensionTrigger: (domElement: Element, props: DatasourceDimensionTriggerProps) => void; renderDimensionEditor: (domElement: Element, props: DatasourceDimensionEditorProps) => void; renderLayerPanel: (domElement: Element, props: DatasourceLayerPanelProps) => void; - getDropTypes: ( + getDropProps: ( props: DatasourceDimensionDropProps & { groupId: string } - ) => DropType | undefined; + ) => { dropType: DropType; nextLabel?: string } | undefined; onDrop: (props: DatasourceDimensionDropHandlerProps) => false | true | { deleted: string }; updateStateOnCloseDimension?: (props: { layerId: string; 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 c0ad934c232e2..fafafa6b6a071 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 33c79c793974b..9682306852ba9 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/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index 89c6d70a217c9..e3a21b596afe1 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 7e87d99fd4f93..2373ed3ba2062 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/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index f42a055b24d0a..b7e50815fd1f7 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 67489802bc31d..7cd4fa8e1253b 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 6a51d4feeb9df..1cf3756160964 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 fa7e1308bac4f..9bf0df612bac4 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 89cd80f4daab5..58268b6ea9d82 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', () => { @@ -66,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; }) { @@ -79,8 +137,8 @@ describe('areLayersLoaded', () => { hasErrors: () => { return hasErrors; }, - isDataLoaded: () => { - return isDataLoaded; + isInitialDataLoadComplete: () => { + return isInitialDataLoadComplete; }, isVisible: () => { return isVisible; @@ -99,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 b16ac704c3715..35c73a2bd2f1c 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, @@ -432,7 +437,7 @@ export const areLayersLoaded = createSelector( layer.isVisible() && layer.showAtZoomLevel(zoom) && !layer.hasErrors() && - !layer.isDataLoaded() + !layer.isInitialDataLoadComplete() ) { return false; } diff --git a/x-pack/plugins/ml/common/types/alerts.ts b/x-pack/plugins/ml/common/types/alerts.ts index 51d06b9906230..7e6e9d89c5a65 100644 --- a/x-pack/plugins/ml/common/types/alerts.ts +++ b/x-pack/plugins/ml/common/types/alerts.ts @@ -15,7 +15,7 @@ export type TopHitsResultsKeys = 'top_record_hits' | 'top_bucket_hits' | 'top_in export interface AlertExecutionResult { count: number; key: number; - key_as_string: string; + alertInstanceKey: string; isInterim: boolean; jobIds: string[]; timestamp: number; @@ -47,10 +47,13 @@ interface BaseAnomalyAlertDoc { export interface RecordAnomalyAlertDoc extends BaseAnomalyAlertDoc { result_type: typeof ANOMALY_RESULT_TYPE.RECORD; function: string; - field_name: string; - by_field_value: string | number; - over_field_value: string | number; - partition_field_value: string | number; + field_name?: string; + by_field_name?: string; + by_field_value?: string | number; + over_field_name?: string; + over_field_value?: string | number; + partition_field_name?: string; + partition_field_value?: string | number; } export interface BucketAnomalyAlertDoc extends BaseAnomalyAlertDoc { diff --git a/x-pack/plugins/ml/common/types/fields.ts b/x-pack/plugins/ml/common/types/fields.ts index 581ce861e8331..047852534965c 100644 --- a/x-pack/plugins/ml/common/types/fields.ts +++ b/x-pack/plugins/ml/common/types/fields.ts @@ -113,7 +113,7 @@ type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; export interface RuntimeField { type: RuntimeType; - script: + script?: | string | { source: string; diff --git a/x-pack/plugins/ml/common/util/anomaly_utils.ts b/x-pack/plugins/ml/common/util/anomaly_utils.ts index 028afee2524c9..68605f29c7be9 100644 --- a/x-pack/plugins/ml/common/util/anomaly_utils.ts +++ b/x-pack/plugins/ml/common/util/anomaly_utils.ts @@ -230,8 +230,6 @@ export function getEntityFieldName(record: AnomalyRecordDoc): string | undefined if (record.partition_field_name !== undefined) { return record.partition_field_name; } - - return undefined; } // Returns the value of the field to use as the entity value from the source record @@ -249,8 +247,6 @@ export function getEntityFieldValue(record: AnomalyRecordDoc): string | number | if (record.partition_field_value !== undefined) { return record.partition_field_value; } - - return undefined; } // Returns the list of partitioning entity fields for the source record as a list diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx index 469327323bee9..fc8798a3af0f5 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_details.tsx @@ -131,20 +131,6 @@ export const AdvancedStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ } if (isRegOrClassJob) { - if (jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION) { - advancedFirstCol.push({ - title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.numTopClasses', { - defaultMessage: 'Top classes', - }), - description: - numTopClasses === -1 - ? i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.allClasses', { - defaultMessage: 'All classes', - }) - : getStringValue(numTopClasses), - }); - } - advancedFirstCol.push({ title: i18n.translate( 'xpack.ml.dataframe.analytics.create.configDetails.numTopFeatureImportanceValues', @@ -170,15 +156,23 @@ export const AdvancedStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ } ); - advancedSecondCol.push({ - title: i18n.translate( - 'xpack.ml.dataframe.analytics.create.configDetails.predictionFieldName', - { - defaultMessage: 'Prediction field name', - } - ), - description: predictionFieldName ? predictionFieldName : `${dependentVariable}_prediction`, - }); + advancedSecondCol.push( + { + title: i18n.translate( + 'xpack.ml.dataframe.analytics.create.configDetails.predictionFieldName', + { + defaultMessage: 'Prediction field name', + } + ), + description: predictionFieldName ? predictionFieldName : `${dependentVariable}_prediction`, + }, + { + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.randomizedSeed', { + defaultMessage: 'Randomized seed', + }), + description: getStringValue(randomizeSeed), + } + ); hyperSecondCol.push( { @@ -205,20 +199,26 @@ export const AdvancedStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ description: `${modelMemoryLimit}`, }); - hyperThirdCol.push( - { - title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.gamma', { - defaultMessage: 'Gamma', - }), - description: getStringValue(gamma), - }, - { - title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.randomizedSeed', { - defaultMessage: 'Randomized seed', - }), - description: getStringValue(randomizeSeed), - } - ); + hyperThirdCol.push({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.gamma', { + defaultMessage: 'Gamma', + }), + description: getStringValue(gamma), + }); + } + + if (jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION) { + advancedThirdCol.push({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.numTopClasses', { + defaultMessage: 'Top classes', + }), + description: + numTopClasses === -1 + ? i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.allClasses', { + defaultMessage: 'All classes', + }) + : getStringValue(numTopClasses), + }); } if (maxNumThreads !== undefined) { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx index 8e25fc961c7c2..71770dcf952d9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx @@ -409,6 +409,34 @@ export const AdvancedStepForm: FC = ({ /> + + + + setFormState({ randomizeSeed: e.target.value === '' ? undefined : +e.target.value }) + } + isInvalid={randomizeSeed !== undefined && typeof randomizeSeed !== 'number'} + value={getNumberValue(randomizeSeed)} + step={1} + /> + + ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx index cd4dd44edfa50..704b2cc77a7f9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/hyper_parameters.tsx @@ -31,7 +31,6 @@ export const HyperParameters: FC = ({ actions, state, advancedParamErrors lambda, maxOptimizationRoundsPerHyperparameter, maxTrees, - randomizeSeed, softTreeDepthLimit, softTreeDepthTolerance, } = state.form; @@ -184,34 +183,6 @@ export const HyperParameters: FC = ({ actions, state, advancedParamErrors /> - - - - setFormState({ randomizeSeed: e.target.value === '' ? undefined : +e.target.value }) - } - isInvalid={randomizeSeed !== undefined && typeof randomizeSeed !== 'number'} - value={getNumberValue(randomizeSeed)} - step={1} - /> - - { try { - return await mlJobService.saveNewDatafeed(this._datafeed_config, this._job_config.job_id); + const tempDatafeed = this._getDatafeedWithFilteredRuntimeMappings(); + return await mlJobService.saveNewDatafeed(tempDatafeed, this._job_config.job_id); } catch (error) { throw error; } @@ -559,6 +562,23 @@ export class JobCreator { return jobRunner; } + private _getDatafeedWithFilteredRuntimeMappings(): Datafeed { + if (this._filterRuntimeMappingsOnSave === false) { + return this._datafeed_config; + } + + const { runtime_mappings: filteredRuntimeMappings } = filterRuntimeMappings( + this._job_config, + this._datafeed_config + ); + + return { + ...this._datafeed_config, + runtime_mappings: + Object.keys(filteredRuntimeMappings).length > 0 ? filteredRuntimeMappings : undefined, + }; + } + public subscribeToProgress(func: ProgressSubscriber) { this._subscribers.push(func); } @@ -645,6 +665,14 @@ export class JobCreator { return JSON.stringify(this._datafeed_config, null, 2); } + public set filterRuntimeMappingsOnSave(filter: boolean) { + this._filterRuntimeMappingsOnSave = filter; + } + + public get filterRuntimeMappingsOnSave(): boolean { + return this._filterRuntimeMappingsOnSave; + } + protected _initPerPartitionCategorization() { if (this._job_config.analysis_config.per_partition_categorization === undefined) { this._job_config.analysis_config.per_partition_categorization = {}; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.test.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.test.ts new file mode 100644 index 0000000000000..43e7d4e45b6e0 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.test.ts @@ -0,0 +1,183 @@ +/* + * 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 { Job, Datafeed } from '../../../../../../../common/types/anomaly_detection_jobs'; +import { filterRuntimeMappings } from './filter_runtime_mappings'; + +function getJob(): Job { + return { + job_id: 'test', + description: '', + groups: [], + analysis_config: { + bucket_span: '15m', + detectors: [ + { + function: 'mean', + field_name: 'responsetime', + }, + ], + influencers: [], + }, + data_description: { + time_field: '@timestamp', + }, + analysis_limits: { + model_memory_limit: '11MB', + }, + model_plot_config: { + enabled: false, + annotations_enabled: false, + }, + }; +} + +function getDatafeed(): Datafeed { + return { + datafeed_id: 'datafeed-test', + job_id: 'dds', + indices: ['farequote-*'], + query: { + bool: { + must: [ + { + match_all: {}, + }, + ], + }, + }, + runtime_mappings: { + responsetime_big: { + type: 'double', + script: { + source: "emit(doc['responsetime'].value * 100.0)", + }, + }, + airline_lower: { + type: 'keyword', + script: { + source: "emit(doc['airline'].value.toLowerCase())", + }, + }, + }, + }; +} + +function getAggs() { + return { + buckets: { + date_histogram: { + field: '@timestamp', + fixed_interval: '90000ms', + }, + aggregations: { + responsetime: { + avg: { + field: 'responsetime_big', + }, + }, + '@timestamp': { + max: { + field: '@timestamp', + }, + }, + }, + }, + }; +} + +describe('filter_runtime_mappings', () => { + describe('filterRuntimeMappings()', () => { + let job: Job; + let datafeed: Datafeed; + beforeEach(() => { + job = getJob(); + datafeed = getDatafeed(); + }); + + test('returns no runtime mappings, no mappings in aggs', () => { + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(0); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(2); + expect(resp.discarded_mappings.responsetime_big).not.toEqual(undefined); + expect(resp.discarded_mappings.airline_lower).not.toEqual(undefined); + }); + + test('returns no runtime mappings, no runtime mappings in datafeed', () => { + datafeed.runtime_mappings = undefined; + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(0); + expect(resp.runtime_mappings.responsetime_big).toEqual(undefined); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(0); + expect(resp.discarded_mappings.airline_lower).toEqual(undefined); + }); + + test('return one runtime mapping and one unused mapping, mappings in aggs', () => { + datafeed.aggregations = getAggs(); + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(1); + expect(resp.runtime_mappings.responsetime_big).not.toEqual(undefined); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(1); + expect(resp.discarded_mappings.airline_lower).not.toEqual(undefined); + }); + + test('return no runtime mappings, no mappings in aggs', () => { + datafeed.aggregations = getAggs(); + datafeed.aggregations!.buckets!.aggregations!.responsetime!.avg!.field! = 'responsetime'; + + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(0); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(2); + expect(resp.discarded_mappings.responsetime_big).not.toEqual(undefined); + expect(resp.discarded_mappings.airline_lower).not.toEqual(undefined); + }); + + test('return one runtime mapping and one unused mapping, no mappings in aggs', () => { + // set the detector field to be a runtime mapping + job.analysis_config.detectors[0].field_name = 'responsetime_big'; + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(1); + expect(resp.runtime_mappings.responsetime_big).not.toEqual(undefined); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(1); + expect(resp.discarded_mappings.airline_lower).not.toEqual(undefined); + }); + + test('return two runtime mappings, no mappings in aggs', () => { + // set the detector field to be a runtime mapping + job.analysis_config.detectors[0].field_name = 'responsetime_big'; + // set the detector by field to be a runtime mapping + job.analysis_config.detectors[0].by_field_name = 'airline_lower'; + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(2); + expect(resp.runtime_mappings.responsetime_big).not.toEqual(undefined); + expect(resp.runtime_mappings.airline_lower).not.toEqual(undefined); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(0); + }); + + test('return two runtime mappings, no mappings in aggs, categorization job', () => { + job.analysis_config.detectors[0].function = 'count'; + // set the detector field to be a runtime mapping + job.analysis_config.detectors[0].field_name = undefined; + // set the detector by field to be a runtime mapping + job.analysis_config.detectors[0].by_field_name = 'mlcategory'; + job.analysis_config.categorization_field_name = 'airline_lower'; + + const resp = filterRuntimeMappings(job, datafeed); + expect(Object.keys(resp.runtime_mappings).length).toEqual(1); + expect(resp.runtime_mappings.airline_lower).not.toEqual(undefined); + + expect(Object.keys(resp.discarded_mappings).length).toEqual(1); + expect(resp.discarded_mappings.responsetime_big).not.toEqual(undefined); + }); + }); +}); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts new file mode 100644 index 0000000000000..5319cd3c3aabc --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts @@ -0,0 +1,104 @@ +/* + * 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. + */ +/* + * 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 { RuntimeMappings } from '../../../../../../../common/types/fields'; +import type { Datafeed, Job } from '../../../../../../../common/types/anomaly_detection_jobs'; + +interface Response { + runtime_mappings: RuntimeMappings; + discarded_mappings: RuntimeMappings; +} + +export function filterRuntimeMappings(job: Job, datafeed: Datafeed): Response { + if (datafeed.runtime_mappings === undefined) { + return { + runtime_mappings: {}, + discarded_mappings: {}, + }; + } + + const usedFields = findFieldsInJob(job, datafeed); + + const { runtimeMappings, discardedMappings } = createMappings( + datafeed.runtime_mappings, + usedFields + ); + + return { runtime_mappings: runtimeMappings, discarded_mappings: discardedMappings }; +} + +function findFieldsInJob(job: Job, datafeed: Datafeed) { + const usedFields = new Set(); + job.analysis_config.detectors.forEach((d) => { + if (d.field_name !== undefined) { + usedFields.add(d.field_name); + } + if (d.by_field_name !== undefined) { + usedFields.add(d.by_field_name); + } + if (d.over_field_name !== undefined) { + usedFields.add(d.over_field_name); + } + if (d.partition_field_name !== undefined) { + usedFields.add(d.partition_field_name); + } + }); + + if (job.analysis_config.categorization_field_name !== undefined) { + usedFields.add(job.analysis_config.categorization_field_name); + } + + if (job.analysis_config.summary_count_field_name !== undefined) { + usedFields.add(job.analysis_config.summary_count_field_name); + } + + if (job.analysis_config.influencers !== undefined) { + job.analysis_config.influencers.forEach((i) => usedFields.add(i)); + } + + const aggs = datafeed.aggregations ?? datafeed.aggs; + if (aggs !== undefined) { + findFieldsInAgg(aggs).forEach((f) => usedFields.add(f)); + } + + return [...usedFields]; +} + +function findFieldsInAgg(obj: Record) { + const fields: string[] = []; + Object.entries(obj).forEach(([key, val]) => { + if (typeof val === 'object' && val !== null) { + fields.push(...findFieldsInAgg(val)); + } else if (typeof val === 'string' && key === 'field') { + fields.push(val); + } + }); + return fields; +} + +function createMappings(rm: RuntimeMappings, usedFieldNames: string[]) { + return { + runtimeMappings: usedFieldNames.reduce((acc, cur) => { + if (rm[cur] !== undefined) { + acc[cur] = rm[cur]; + } + return acc; + }, {} as RuntimeMappings), + discardedMappings: Object.keys(rm).reduce((acc, cur) => { + if (usedFieldNames.includes(cur) === false && rm[cur] !== undefined) { + acc[cur] = rm[cur]; + } + return acc; + }, {} as RuntimeMappings), + }; +} diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.test.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.test.ts index 261fac7b620ba..f029fa24f9607 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.test.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.test.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { resolveTimeInterval } from './alerting_service'; +import { resolveBucketSpanInSeconds } from './alerting_service'; describe('Alerting Service', () => { test('should resolve maximum bucket interval', () => { - expect(resolveTimeInterval(['15m', '1h', '6h', '90s'])).toBe('43200s'); + expect(resolveBucketSpanInSeconds(['15m', '1h', '6h', '90s'])).toBe(43200); }); }); diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts index 5ef883cc50fbb..6e7cd77e450bc 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts @@ -7,6 +7,9 @@ import Boom from '@hapi/boom'; import rison from 'rison-node'; +import { ElasticsearchClient } from 'kibana/server'; +import moment from 'moment'; +import { Duration } from 'moment/moment'; import { MlClient } from '../ml_client'; import { MlAnomalyDetectionAlertParams, @@ -25,6 +28,8 @@ import { import { parseInterval } from '../../../common/util/parse_interval'; import { AnomalyDetectionAlertContext } from './register_anomaly_detection_alert_type'; import { MlJobsResponse } from '../../../common/types/job_service'; +import { ANOMALY_SCORE_MATCH_GROUP_ID } from '../../../common/constants/alerts'; +import { getEntityFieldName, getEntityFieldValue } from '../../../common/util/anomaly_utils'; function isDefined(argument: T | undefined | null): argument is T { return argument !== undefined && argument !== null; @@ -34,22 +39,23 @@ function isDefined(argument: T | undefined | null): argument is T { * Resolves the longest bucket span from the list and multiply it by 2. * @param bucketSpans Collection of bucket spans */ -export function resolveTimeInterval(bucketSpans: string[]): string { - return `${ +export function resolveBucketSpanInSeconds(bucketSpans: string[]): number { + return ( Math.max( ...bucketSpans .map((b) => parseInterval(b)) .filter(isDefined) .map((v) => v.asSeconds()) ) * 2 - }s`; + ); } /** * Alerting related server-side methods * @param mlClient + * @param esClient */ -export function alertingServiceProvider(mlClient: MlClient) { +export function alertingServiceProvider(mlClient: MlClient, esClient: ElasticsearchClient) { const getAggResultsLabel = (resultType: AnomalyResultType) => { return { aggGroupLabel: `${resultType}_results` as PreviewResultsKeys, @@ -177,10 +183,14 @@ export function alertingServiceProvider(mlClient: MlClient) { 'is_interim', 'function', 'field_name', + 'by_field_name', 'by_field_value', + 'over_field_name', 'over_field_value', + 'partition_field_name', 'partition_field_value', 'job_id', + 'detector_index', ], }, size: 3, @@ -257,14 +267,31 @@ export function alertingServiceProvider(mlClient: MlClient) { }; }; + /** + * Provides unique key for the anomaly result. + */ + const getAlertInstanceKey = (source: any): string => { + let alertInstanceKey = `${source.job_id}_${source.timestamp}`; + if (source.result_type === ANOMALY_RESULT_TYPE.INFLUENCER) { + alertInstanceKey += `_${source.influencer_field_name}_${source.influencer_field_value}`; + } else if (source.result_type === ANOMALY_RESULT_TYPE.RECORD) { + const fieldName = getEntityFieldName(source); + const fieldValue = getEntityFieldValue(source); + alertInstanceKey += `_${source.detector_index}_${source.function}_${fieldName}_${fieldValue}`; + } + return alertInstanceKey; + }; + /** * Builds a request body - * @param params - * @param previewTimeInterval + * @param params - Alert params + * @param previewTimeInterval - Relative time interval to test the alert condition + * @param checkIntervalGap - Interval between alert executions */ const fetchAnomalies = async ( params: MlAnomalyDetectionAlertParams, - previewTimeInterval?: string + previewTimeInterval?: string, + checkIntervalGap?: Duration ): Promise => { const jobAndGroupIds = [ ...(params.jobSelection.jobIds ?? []), @@ -281,9 +308,14 @@ export function alertingServiceProvider(mlClient: MlClient) { return; } - const lookBackTimeInterval = resolveTimeInterval( - jobsResponse.map((v) => v.analysis_config.bucket_span) - ); + /** + * The check interval might be bigger than the 2x bucket span. + * We need to check the biggest time range to make sure anomalies are not missed. + */ + const lookBackTimeInterval = `${Math.max( + resolveBucketSpanInSeconds(jobsResponse.map((v) => v.analysis_config.bucket_span)), + checkIntervalGap ? checkIntervalGap.asSeconds() : 0 + )}s`; const jobIds = jobsResponse.map((v) => v.job_id); @@ -370,19 +402,22 @@ export function alertingServiceProvider(mlClient: MlClient) { const aggTypeResults = v[resultsLabel.aggGroupLabel]; const requestedAnomalies = aggTypeResults[resultsLabel.topHitsLabel].hits.hits; + const topAnomaly = requestedAnomalies[0]; + const alertInstanceKey = getAlertInstanceKey(topAnomaly._source); + return { count: aggTypeResults.doc_count, key: v.key, - key_as_string: v.key_as_string, + alertInstanceKey, jobIds: [...new Set(requestedAnomalies.map((h) => h._source.job_id))], isInterim: requestedAnomalies.some((h) => h._source.is_interim), - timestamp: requestedAnomalies[0]._source.timestamp, - timestampIso8601: requestedAnomalies[0].fields.timestamp_iso8601[0], - timestampEpoch: requestedAnomalies[0].fields.timestamp_epoch[0], - score: requestedAnomalies[0].fields.score[0], + timestamp: topAnomaly._source.timestamp, + timestampIso8601: topAnomaly.fields.timestamp_iso8601[0], + timestampEpoch: topAnomaly.fields.timestamp_epoch[0], + score: topAnomaly.fields.score[0], bucketRange: { - start: requestedAnomalies[0].fields.start[0], - end: requestedAnomalies[0].fields.end[0], + start: topAnomaly.fields.start[0], + end: topAnomaly.fields.end[0], }, topRecords: v.record_results.top_record_hits.hits.hits.map((h) => ({ ...h._source, @@ -479,13 +514,24 @@ export function alertingServiceProvider(mlClient: MlClient) { /** * Return the result of an alert condition execution. * - * @param params + * @param params - Alert params + * @param publicBaseUrl + * @param alertId - Alert ID + * @param startedAt + * @param previousStartedAt */ execute: async ( params: MlAnomalyDetectionAlertParams, - publicBaseUrl: string | undefined + publicBaseUrl: string | undefined, + alertId: string, + startedAt: Date, + previousStartedAt: Date | null ): Promise => { - const res = await fetchAnomalies(params); + const checkIntervalGap = previousStartedAt + ? moment.duration(moment(startedAt).diff(previousStartedAt)) + : undefined; + + const res = await fetchAnomalies(params, undefined, checkIntervalGap); if (!res) { throw new Error('No results found'); @@ -496,12 +542,65 @@ export function alertingServiceProvider(mlClient: MlClient) { const anomalyExplorerUrl = buildExplorerUrl(result, params.resultType as AnomalyResultType); - return { + const executionResult = { ...result, - name: result.key_as_string, + name: result.alertInstanceKey, anomalyExplorerUrl, kibanaBaseUrl: publicBaseUrl!, }; + + let kibanaEventLogCount = 0; + try { + // Check kibana-event-logs for presence of this alert instance + const kibanaLogResults = await esClient.count({ + index: '.kibana-event-log-*', + body: { + query: { + bool: { + must: [ + { + term: { + 'kibana.alerting.action_group_id': { + value: ANOMALY_SCORE_MATCH_GROUP_ID, + }, + }, + }, + { + term: { + 'kibana.alerting.instance_id': { + value: executionResult.name, + }, + }, + }, + { + nested: { + path: 'kibana.saved_objects', + query: { + term: { + 'kibana.saved_objects.id': { + value: alertId, + }, + }, + }, + }, + }, + ], + }, + }, + }, + }); + + kibanaEventLogCount = kibanaLogResults.body.count; + } catch (e) { + // eslint-disable-next-line no-console + console.log('Unable to check kibana event logs', e); + } + + if (kibanaEventLogCount > 0) { + return; + } + + return executionResult; }, /** * Checks how often the alert condition will fire an alert instance diff --git a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts index 6f8fa59aa231e..30a92c02cefc3 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts @@ -123,13 +123,19 @@ export function registerAnomalyDetectionAlertType({ }, producer: PLUGIN_ID, minimumLicenseRequired: MINIMUM_FULL_LICENSE, - async executor({ services, params }) { + async executor({ services, params, alertId, state, previousStartedAt, startedAt }) { const fakeRequest = {} as KibanaRequest; const { execute } = mlSharedServices.alertingServiceProvider( services.savedObjectsClient, fakeRequest ); - const executionResult = await execute(params, publicBaseUrl); + const executionResult = await execute( + params, + publicBaseUrl, + alertId, + startedAt, + previousStartedAt + ); if (executionResult) { const alertInstanceName = executionResult.name; diff --git a/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts b/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts index 5c9106d78595f..371c5435f91de 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_ml_alerts.ts @@ -5,12 +5,14 @@ * 2.0. */ +import { Logger } from 'kibana/server'; import { AlertingPlugin } from '../../../../alerts/server'; import { registerAnomalyDetectionAlertType } from './register_anomaly_detection_alert_type'; import { SharedServices } from '../../shared_services'; export interface RegisterAlertParams { alerts: AlertingPlugin['setup']; + logger: Logger; mlSharedServices: SharedServices; publicBaseUrl: string | undefined; } diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index 10ed70d7f7396..24fac9184cc27 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -211,6 +211,7 @@ export class MlServerPlugin if (plugins.alerts) { registerMlAlerts({ alerts: plugins.alerts, + logger: this.log, mlSharedServices: sharedServices, publicBaseUrl: coreSetup.http.basePath.publicBaseUrl, }); diff --git a/x-pack/plugins/ml/server/routes/alerting.ts b/x-pack/plugins/ml/server/routes/alerting.ts index 7b7f3a7db9723..a268a5200b35e 100644 --- a/x-pack/plugins/ml/server/routes/alerting.ts +++ b/x-pack/plugins/ml/server/routes/alerting.ts @@ -30,9 +30,9 @@ export function alertingRoutes({ router, routeGuard }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response }) => { + routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response, client }) => { try { - const alertingService = alertingServiceProvider(mlClient); + const alertingService = alertingServiceProvider(mlClient, client.asInternalUser); const result = await alertingService.preview(request.body); diff --git a/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts b/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts index 318dac200a877..cbe22478e12d6 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/alerting_service.ts @@ -20,7 +20,9 @@ export function getAlertingServiceProvider(getGuards: GetGuards) { return await getGuards(request, savedObjectsClient) .isFullLicense() .hasMlCapabilities(['canGetJobs']) - .ok(({ mlClient }) => alertingServiceProvider(mlClient).preview(...args)); + .ok(({ mlClient, scopedClient }) => + alertingServiceProvider(mlClient, scopedClient.asInternalUser).preview(...args) + ); }, execute: async ( ...args: Parameters @@ -28,7 +30,9 @@ export function getAlertingServiceProvider(getGuards: GetGuards) { return await getGuards(request, savedObjectsClient) .isFullLicense() .hasMlCapabilities(['canGetJobs']) - .ok(({ mlClient }) => alertingServiceProvider(mlClient).execute(...args)); + .ok(({ mlClient, scopedClient }) => + alertingServiceProvider(mlClient, scopedClient.asInternalUser).execute(...args) + ); }, }; }, diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 8b1e91e2caf67..18c9610656648 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 f0b58b4101505..3088e2a75c349 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 3f23035f52649..87850f893797a 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 8437faf2258b9..6639b4f2f6a48 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 { const timestamp = Date.now(); @@ -145,13 +145,13 @@ describe('get_all_stats', () => { logstash: { count: 1, versions: [{ version: '2.3.4-beta2', count: 1 }], - os: { - platforms: [], - platformReleases: [], - distros: [], - distroReleases: [], + cluster_stats: { + collection_types: { + internal_collection: 1, + }, + pipelines: {}, + plugins: [], }, - cloud: undefined, }, }, }, @@ -188,7 +188,7 @@ describe('get_all_stats', () => { it('handles response', () => { const clusters = handleAllStats(esClusters as ESClusterStats[], { kibana: (kibanaStats as unknown) as KibanaStats, - logstash: (logstashStats as unknown) as ClustersHighLevelStats, + logstash: (logstashStats as unknown) as LogstashStatsByClusterUuid, beats: {}, }); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts index 67a21dc04fd55..60b107cb29342 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts @@ -19,7 +19,7 @@ import { import { getElasticsearchStats, ESClusterStats } from './get_es_stats'; import { getKibanaStats, KibanaStats } from './get_kibana_stats'; import { getBeatsStats, BeatsStatsByClusterUuid } from './get_beats_stats'; -import { getHighLevelStats, ClustersHighLevelStats } from './get_high_level_stats'; +import { getLogstashStats, LogstashStatsByClusterUuid } from './get_logstash_stats'; /** * Get statistics for all products joined by Elasticsearch cluster. @@ -38,7 +38,7 @@ export async function getAllStats( const [esClusters, kibana, logstash, beats] = await Promise.all([ getElasticsearchStats(callCluster, clusterUuids, maxBucketSize), // cluster_stats, stack_stats.xpack, cluster_name/uuid, license, version getKibanaStats(callCluster, clusterUuids, start, end, maxBucketSize), // stack_stats.kibana - getHighLevelStats(callCluster, clusterUuids, start, end, LOGSTASH_SYSTEM_ID, maxBucketSize), // stack_stats.logstash + getLogstashStats(callCluster, clusterUuids), // stack_stats.logstash getBeatsStats(callCluster, clusterUuids, start, end), // stack_stats.beats ]); @@ -63,7 +63,7 @@ export function handleAllStats( beats, }: { kibana: KibanaStats; - logstash: ClustersHighLevelStats; + logstash: LogstashStatsByClusterUuid; beats: BeatsStatsByClusterUuid; } ) { diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts index f7252c9d89364..63188be142fdd 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts @@ -53,7 +53,7 @@ type Counter = Map; * @param {Map} map Map to update the counter for the {@code key}. * @param {String} key The key to increment a counter for. */ -function incrementByKey(map: Counter, key?: string) { +export function incrementByKey(map: Counter, key?: string) { if (!key) { return; } @@ -207,7 +207,7 @@ function groupInstancesByCluster( * { [keyName]: key2, count: value2 } * ] */ -function mapToList(map: Map, keyName: string): T[] { +export function mapToList(map: Map, keyName: string): T[] { const list: T[] = []; for (const [key, count] of map) { diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts new file mode 100644 index 0000000000000..f2f0c37255d92 --- /dev/null +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts @@ -0,0 +1,401 @@ +/* + * 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 { + fetchLogstashStats, + fetchLogstashState, + processStatsResults, + processLogstashStateResults, +} from './get_logstash_stats'; +import sinon from 'sinon'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const logstashStatsResultSet = require('./__mocks__/fixtures/logstash_stats_results'); + +const resultsMap = new Map(); + +// Load data for state results. +['1n1p', '1nmp', 'mnmp'].forEach((data) => { + // eslint-disable-next-line @typescript-eslint/no-var-requires + resultsMap.set(data, require(`./__mocks__/fixtures/logstash_state_results_${data}`)); +}); + +const getBaseOptions = () => ({ + clusters: {}, + allEphemeralIds: {}, + versions: {}, + plugins: {}, +}); + +describe('Get Logstash Stats', () => { + const clusterUuids = ['aCluster', 'bCluster', 'cCluster']; + let callCluster = sinon.stub(); + + beforeEach(() => { + callCluster = sinon.stub(); + }); + + describe('fetchLogstashState', () => { + const clusterUuid = 'a'; + const ephemeralIds = ['a', 'b', 'c']; + it('should create the logstash state query correctly', async () => { + const expected = { + bool: { + filter: [ + { + terms: { + 'logstash_state.pipeline.ephemeral_id': ['a', 'b', 'c'], + }, + }, + { + bool: { + must: { + term: { type: 'logstash_state' }, + }, + }, + }, + ], + }, + }; + + await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, {} as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + expect(api).toEqual('search'); + expect(body.query).toEqual(expected); + }); + + it('should set `from: 0, to: 10000` in the query', async () => { + await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, {} as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + expect(api).toEqual('search'); + expect(body.from).toEqual(0); + expect(body.size).toEqual(10000); + }); + + it('should set `from: 10000, to: 10000` in the query', async () => { + await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, { + page: 1, + } as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + + expect(api).toEqual('search'); + expect(body.from).toEqual(10000); + expect(body.size).toEqual(10000); + }); + + it('should set `from: 20000, to: 10000` in the query', async () => { + await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, { + page: 2, + } as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + + expect(api).toEqual('search'); + expect(body.from).toEqual(20000); + expect(body.size).toEqual(10000); + }); + }); + + describe('fetchLogstashStats', () => { + it('should set `from: 0, to: 10000` in the query', async () => { + await fetchLogstashStats(callCluster, clusterUuids, {} as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + + expect(api).toEqual('search'); + expect(body.from).toEqual(0); + expect(body.size).toEqual(10000); + }); + + it('should set `from: 10000, to: 10000` in the query', async () => { + await fetchLogstashStats(callCluster, clusterUuids, { page: 1 } as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + + expect(api).toEqual('search'); + expect(body.from).toEqual(10000); + expect(body.size).toEqual(10000); + }); + + it('should set `from: 20000, to: 10000` in the query', async () => { + await fetchLogstashStats(callCluster, clusterUuids, { page: 2 } as any); + const { args } = callCluster.firstCall; + const [api, { body }] = args; + + expect(api).toEqual('search'); + expect(body.from).toEqual(20000); + expect(body.size).toEqual(10000); + }); + }); + + describe('processLogstashStatsResults', () => { + it('should summarize empty results', () => { + const resultsEmpty = undefined; + + const options = getBaseOptions(); + processStatsResults(resultsEmpty as any, options); + + expect(options.clusters).toStrictEqual({}); + }); + + it('should summarize single result with some missing fields', () => { + const results = { + hits: { + hits: [ + { + _source: { + type: 'logstash_stats', + cluster_uuid: 'FlV4ckTxQ0a78hmBkzzc9A', + logstash_stats: { + logstash: { + uuid: '61de393a-f2b6-4b6c-8cea-22661f9c4134', + }, + pipelines: [ + { + id: 'main', + ephemeral_id: 'cf37c6fa-2f1a-41e2-9a89-36b420a8b9a5', + queue: { + type: 'memory', + }, + }, + ], + }, + }, + }, + ], + }, + }; + + const options = getBaseOptions(); + processStatsResults(results as any, options); + + expect(options.clusters).toStrictEqual({ + FlV4ckTxQ0a78hmBkzzc9A: { + count: 1, + cluster_stats: { + plugins: [], + collection_types: { + internal_collection: 1, + }, + pipelines: {}, + queues: { + memory: 1, + }, + }, + versions: [], + }, + }); + }); + + it('should summarize stats from hits across multiple result objects', () => { + const options = getBaseOptions(); + + // logstashStatsResultSet is an array of many small query results + logstashStatsResultSet.forEach((results: any) => { + processStatsResults(results, options); + }); + + resultsMap.forEach((value: string[], clusterUuid: string) => { + value.forEach((results: any) => { + processLogstashStateResults(results, clusterUuid, options); + }); + }); + + expect(options.clusters).toStrictEqual({ + '1n1p': { + count: 1, + versions: [ + { + count: 1, + version: '7.10.0', + }, + ], + cluster_stats: { + collection_types: { + internal_collection: 1, + }, + pipelines: { + batch_size_avg: 125, + batch_size_max: 125, + batch_size_min: 125, + batch_size_total: 125, + count: 1, + sources: { + file: true, + }, + workers_avg: 1, + workers_max: 1, + workers_min: 1, + workers_total: 1, + }, + plugins: [ + { + count: 1, + name: 'logstash-input-stdin', + }, + { + count: 1, + name: 'logstash-input-elasticsearch', + }, + { + count: 3, + name: 'logstash-filter-mutate', + }, + { + count: 3, + name: 'logstash-filter-ruby', + }, + { + count: 1, + name: 'logstash-filter-split', + }, + { + count: 1, + name: 'logstash-filter-elasticsearch', + }, + { + count: 1, + name: 'logstash-filter-aggregate', + }, + { + count: 1, + name: 'logstash-filter-drop', + }, + { + count: 1, + name: 'logstash-output-elasticsearch', + }, + { + count: 1, + name: 'logstash-output-stdout', + }, + ], + queues: { + memory: 1, + }, + }, + }, + '1nmp': { + count: 1, + versions: [ + { + count: 1, + version: '7.8.0', + }, + ], + cluster_stats: { + collection_types: { + metricbeat: 1, + }, + pipelines: { + batch_size_avg: 406.5, + batch_size_max: 1251, + batch_size_min: 125, + batch_size_total: 1626, + count: 4, + sources: { + xpack: true, + }, + workers_avg: 17.25, + workers_max: 44, + workers_min: 1, + workers_total: 69, + }, + plugins: [ + { + count: 4, + name: 'logstash-input-stdin', + }, + { + count: 4, + name: 'logstash-output-stdout', + }, + ], + queues: { + memory: 3, + persisted: 1, + }, + }, + }, + mnmp: { + count: 3, + versions: [ + { + count: 1, + version: '7.9.2', + }, + { + count: 1, + version: '7.9.1', + }, + { + count: 1, + version: '7.10.0', + }, + ], + cluster_stats: { + collection_types: { + internal_collection: 3, + }, + pipelines: { + batch_size_avg: 33.294117647058826, + batch_size_max: 125, + batch_size_min: 1, + batch_size_total: 566, + count: 17, + sources: { + file: true, + string: true, + }, + workers_avg: 7.411764705882353, + workers_max: 16, + workers_min: 1, + workers_total: 126, + }, + plugins: [ + { + count: 1, + name: 'logstash-input-stdin', + }, + { + count: 1, + name: 'logstash-filter-clone', + }, + { + count: 3, + name: 'logstash-output-pipeline', + }, + { + count: 2, + name: 'logstash-input-pipeline', + }, + { + count: 16, + name: 'logstash-filter-sleep', + }, + { + count: 14, + name: 'logstash-output-stdout', + }, + { + count: 14, + name: 'logstash-input-generator', + }, + ], + queues: { + memory: 3, + persisted: 14, + }, + }, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts new file mode 100644 index 0000000000000..93c69c644c064 --- /dev/null +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts @@ -0,0 +1,418 @@ +/* + * 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 { SearchResponse } from 'elasticsearch'; +import { LegacyAPICaller } from 'kibana/server'; +import { createQuery } from './create_query'; +import { mapToList } from './get_high_level_stats'; +import { incrementByKey } from './get_high_level_stats'; + +import { INDEX_PATTERN_LOGSTASH, TELEMETRY_QUERY_SOURCE } from '../../common/constants'; + +type Counter = Map; + +const HITS_SIZE = 10000; // maximum hits to receive from ES with each search + +export interface LogstashBaseStats { + // stats + versions: Array<{ version: string; count: number }>; + count: number; + + cluster_stats?: { + collection_types?: { [collection_type_type: string]: number }; + queues?: { [queue_type: string]: number }; + plugins?: Array<{ name: string; count: number }>; + pipelines?: { + count?: number; + batch_size_max?: number; + batch_size_avg?: number; + batch_size_min?: number; + batch_size_total?: number; + workers_max?: number; + workers_avg?: number; + workers_min?: number; + workers_total?: number; + sources?: { [source_type: string]: boolean }; + }; + }; +} + +const getLogstashBaseStats = () => ({ + versions: [], + count: 0, + cluster_stats: { + pipelines: {}, + plugins: [], + }, +}); + +export interface LogstashStats { + cluster_uuid: string; + source_node: string; + type: string; + agent?: { + type: string; + }; + logstash_stats?: { + pipelines?: [ + { + id?: string; + ephemeral_id: string; + queue?: { + type: string; + }; + } + ]; + logstash?: { + version?: string; + uuid?: string; + snapshot?: string; + }; + }; +} + +export interface LogstashState { + logstash_state?: { + pipeline?: { + batch_size?: number; + workers?: number; + representation?: { + graph?: { + vertices?: [ + { + config_name?: string; + plugin_type?: string; + meta?: { + source?: { + protocol?: string; + }; + }; + } + ]; + }; + }; + }; + }; +} + +export interface LogstashProcessOptions { + clusters: { [clusterUuid: string]: LogstashBaseStats }; + allEphemeralIds: { [clusterUuid: string]: string[] }; + versions: { [clusterUuid: string]: Counter }; + plugins: { [clusterUuid: string]: Counter }; +} + +/* + * Update a clusters object with processed Logstash stats + * @param {Array} results - array of LogstashStats docs from ES + * @param {Object} clusters - LogstashBaseStats in an object keyed by the cluster UUIDs + * @param {Object} allEphemeralIds - EphemeralIds in an object keyed by cluster UUIDs to track the pipelines for the cluster + * @param {Object} versions - Versions in an object keyed by cluster UUIDs to track the logstash versions for the cluster + * @param {Object} plugins - plugin information keyed by cluster UUIDs to count the unique plugins + */ +export function processStatsResults( + results: SearchResponse, + { clusters, allEphemeralIds, versions, plugins }: LogstashProcessOptions +) { + const currHits = results?.hits?.hits || []; + currHits.forEach((hit) => { + const clusterUuid = hit._source.cluster_uuid; + if (clusters[clusterUuid] === undefined) { + clusters[clusterUuid] = getLogstashBaseStats(); + versions[clusterUuid] = new Map(); + plugins[clusterUuid] = new Map(); + } + const logstashStats = hit._source.logstash_stats; + const clusterStats = clusters[clusterUuid].cluster_stats; + + if (clusterStats !== undefined && logstashStats !== undefined) { + clusters[clusterUuid].count = (clusters[clusterUuid].count || 0) + 1; + + const thisVersion = logstashStats.logstash?.version; + const a: Counter = versions[clusterUuid]; + incrementByKey(a, thisVersion); + clusters[clusterUuid].versions = mapToList(a, 'version'); + + // Internal Collection has no agent field, so default to 'internal_collection' + let thisCollectionType = hit._source.agent?.type; + if (thisCollectionType === undefined) { + thisCollectionType = 'internal_collection'; + } + if (!clusterStats.hasOwnProperty('collection_types')) { + clusterStats.collection_types = {}; + } + clusterStats.collection_types![thisCollectionType] = + (clusterStats.collection_types![thisCollectionType] || 0) + 1; + + const theseEphemeralIds: string[] = []; + const pipelines = logstashStats.pipelines || []; + + pipelines.forEach((pipeline) => { + const thisQueueType = pipeline.queue?.type; + if (thisQueueType !== undefined) { + if (!clusterStats.hasOwnProperty('queues')) { + clusterStats.queues = {}; + } + clusterStats.queues![thisQueueType] = (clusterStats.queues![thisQueueType] || 0) + 1; + } + + const ephemeralId = pipeline.ephemeral_id; + if (ephemeralId !== undefined) { + theseEphemeralIds.push(ephemeralId); + } + }); + allEphemeralIds[clusterUuid] = theseEphemeralIds; + } + }); +} + +/* + * Update a clusters object with logstash state details + * @param {Array} results - array of LogstashState docs from ES + * @param {Object} clusters - LogstashBaseStats in an object keyed by the cluster UUIDs + * @param {Object} plugins - plugin information keyed by cluster UUIDs to count the unique plugins + */ +export function processLogstashStateResults( + results: SearchResponse, + clusterUuid: string, + { clusters, plugins }: LogstashProcessOptions +) { + const currHits = results?.hits?.hits || []; + const clusterStats = clusters[clusterUuid].cluster_stats; + const pipelineStats = clusters[clusterUuid].cluster_stats?.pipelines; + + currHits.forEach((hit) => { + const thisLogstashStatePipeline = hit._source.logstash_state?.pipeline; + + if (pipelineStats !== undefined && thisLogstashStatePipeline !== undefined) { + pipelineStats.count = (pipelineStats.count || 0) + 1; + + const thisPipelineBatchSize = thisLogstashStatePipeline.batch_size; + + if (thisPipelineBatchSize !== undefined) { + pipelineStats.batch_size_total = + (pipelineStats.batch_size_total || 0) + thisPipelineBatchSize; + pipelineStats.batch_size_max = pipelineStats.batch_size_max || 0; + pipelineStats.batch_size_min = pipelineStats.batch_size_min || 0; + pipelineStats.batch_size_avg = pipelineStats.batch_size_total / pipelineStats.count; + + if (thisPipelineBatchSize > pipelineStats.batch_size_max) { + pipelineStats.batch_size_max = thisPipelineBatchSize; + } + if ( + pipelineStats.batch_size_min === 0 || + thisPipelineBatchSize < pipelineStats.batch_size_min + ) { + pipelineStats.batch_size_min = thisPipelineBatchSize; + } + } + + const thisPipelineWorkers = thisLogstashStatePipeline.workers; + if (thisPipelineWorkers !== undefined) { + pipelineStats.workers_total = (pipelineStats.workers_total || 0) + thisPipelineWorkers; + pipelineStats.workers_max = pipelineStats.workers_max || 0; + pipelineStats.workers_min = pipelineStats.workers_min || 0; + pipelineStats.workers_avg = pipelineStats.workers_total / pipelineStats.count; + + if (thisPipelineWorkers > pipelineStats.workers_max) { + pipelineStats.workers_max = thisPipelineWorkers; + } + if (pipelineStats.workers_min === 0 || thisPipelineWorkers < pipelineStats.workers_min) { + pipelineStats.workers_min = thisPipelineWorkers; + } + } + + // Extract the vertices object from the pipeline representation. From this, we can + // retrieve the source of the pipeline element on the configuration(from file, string, or + // x-pack-config-management), and the input, filter and output plugins from that pipeline. + const vertices = thisLogstashStatePipeline.representation?.graph?.vertices; + + if (vertices !== undefined) { + vertices.forEach((vertex) => { + const configName = vertex.config_name; + const pluginType = vertex.plugin_type; + let pipelineConfig = vertex.meta?.source?.protocol; + + if (pipelineConfig !== undefined) { + if (pipelineConfig === 'string' || pipelineConfig === 'str') { + pipelineConfig = 'string'; + } else if (pipelineConfig === 'x-pack-config-management') { + pipelineConfig = 'xpack'; + } else { + pipelineConfig = 'file'; + } + if (!pipelineStats.hasOwnProperty('sources')) { + pipelineStats.sources = {}; + } + pipelineStats.sources![pipelineConfig] = true; + } + if (configName !== undefined && pluginType !== undefined) { + incrementByKey(plugins[clusterUuid], `logstash-${pluginType}-${configName}`); + } + }); + } + } + }); + if (clusterStats !== undefined) { + clusterStats.plugins = mapToList(plugins[clusterUuid], 'name'); + } +} + +export async function fetchLogstashStats( + callCluster: LegacyAPICaller, + clusterUuids: string[], + { page = 0, ...options }: { page?: number } & LogstashProcessOptions +): Promise { + const params = { + headers: { + 'X-QUERY-SOURCE': TELEMETRY_QUERY_SOURCE, + }, + index: INDEX_PATTERN_LOGSTASH, + ignoreUnavailable: true, + filterPath: [ + 'hits.hits._source.cluster_uuid', + 'hits.hits._source.type', + 'hits.hits._source.source_node', + 'hits.hits._source.agent.type', + 'hits.hits._source.logstash_stats.pipelines.id', + 'hits.hits._source.logstash_stats.pipelines.ephemeral_id', + 'hits.hits._source.logstash_stats.pipelines.queue.type', + 'hits.hits._source.logstash_stats.logstash.version', + 'hits.hits._source.logstash_stats.logstash.uuid', + ], + body: { + query: createQuery({ + filters: [ + { terms: { cluster_uuid: clusterUuids } }, + { + bool: { + must: { term: { type: 'logstash_stats' } }, + }, + }, + ], + }), + from: page * HITS_SIZE, + collapse: { field: 'logstash_stats.logstash.uuid' }, + sort: [{ ['logstash_stats.timestamp']: { order: 'desc', unmapped_type: 'long' } }], + size: HITS_SIZE, + }, + }; + + const results = await callCluster>('search', params); + const hitsLength = results?.hits?.hits.length || 0; + + if (hitsLength > 0) { + // further augment the clusters object with more stats + processStatsResults(results, options); + + if (hitsLength === HITS_SIZE) { + // call recursively + const nextOptions = { + page: page + 1, + ...options, + }; + + // returns a promise and keeps the caller blocked from returning until the entire clusters object is built + return fetchLogstashStats(callCluster, clusterUuids, nextOptions); + } + } + return Promise.resolve(); +} + +export async function fetchLogstashState( + callCluster: LegacyAPICaller, + clusterUuid: string, + ephemeralIds: string[], + { page = 0, ...options }: { page?: number } & LogstashProcessOptions +): Promise { + const params = { + headers: { + 'X-QUERY-SOURCE': TELEMETRY_QUERY_SOURCE, + }, + index: INDEX_PATTERN_LOGSTASH, + ignoreUnavailable: true, + filterPath: [ + 'hits.hits._source.logstash_state.pipeline.batch_size', + 'hits.hits._source.logstash_state.pipeline.workers', + 'hits.hits._source.logstash_state.pipeline.representation.graph.vertices.config_name', + 'hits.hits._source.logstash_state.pipeline.representation.graph.vertices.plugin_type', + 'hits.hits._source.logstash_state.pipeline.representation.graph.vertices.meta.source.protocol', + 'hits.hits._source.logstash_state.pipeline.representation.graph.vertices', + 'hits.hits._source.type', + ], + body: { + query: createQuery({ + filters: [ + { terms: { 'logstash_state.pipeline.ephemeral_id': ephemeralIds } }, + { + bool: { + must: { term: { type: 'logstash_state' } }, + }, + }, + ], + }), + from: page * HITS_SIZE, + collapse: { field: 'logstash_state.pipeline.ephemeral_id' }, + sort: [{ ['timestamp']: { order: 'desc', unmapped_type: 'long' } }], + size: HITS_SIZE, + }, + }; + + const results = await callCluster>('search', params); + const hitsLength = results?.hits?.hits.length || 0; + if (hitsLength > 0) { + // further augment the clusters object with more stats + processLogstashStateResults(results, clusterUuid, options); + + if (hitsLength === HITS_SIZE) { + // call recursively + const nextOptions = { + page: page + 1, + ...options, + }; + + // returns a promise and keeps the caller blocked from returning until the entire clusters object is built + return fetchLogstashState(callCluster, clusterUuid, ephemeralIds, nextOptions); + } + } + return Promise.resolve(); +} + +export interface LogstashStatsByClusterUuid { + [clusterUuid: string]: LogstashBaseStats; +} + +/* + * Call the function for fetching and summarizing Logstash stats + * @return {Object} - Logstash stats in an object keyed by the cluster UUIDs + */ +export async function getLogstashStats( + callCluster: LegacyAPICaller, + clusterUuids: string[] +): Promise { + const options: LogstashProcessOptions = { + clusters: {}, // the result object to be built up + allEphemeralIds: {}, + versions: {}, + plugins: {}, + }; + + await fetchLogstashStats(callCluster, clusterUuids, options); + await Promise.all( + clusterUuids.map(async (clusterUuid) => { + if (options.clusters[clusterUuid] !== undefined) { + await fetchLogstashState( + callCluster, + clusterUuid, + options.allEphemeralIds[clusterUuid], + options + ); + } + }) + ); + return options.clusters; +} diff --git a/x-pack/plugins/osquery/common/ecs/rule/index.ts b/x-pack/plugins/osquery/common/ecs/rule/index.ts index 3a764a836e9b3..51d7722a3ecde 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/reporting/server/export_types/printable_pdf/lib/pdf/get_font.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/pdf/get_font.test.ts new file mode 100644 index 0000000000000..e2d44b43b674b --- /dev/null +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/pdf/get_font.test.ts @@ -0,0 +1,34 @@ +/* + * 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 { getFont } from './get_font'; + +describe('getFont', () => { + it(`returns 'noto-cjk' when matching cjk characters`, () => { + const cjkStrings = [ + 'vi-Hani: 关', + 'ko: 全', + 'ja: 入', + 'zh-Hant-HK: 免', + 'zh-Hant: 令', + 'zh-Hans: 令', + 'random: おあいい 漢字 あい 抵 令', + String.fromCharCode(0x4ee4), + String.fromCodePoint(0x9aa8), + ]; + + for (const cjkString of cjkStrings) { + expect(getFont(cjkString)).toBe('noto-cjk'); + } + }); + + it(`returns 'Roboto' for non Han characters`, () => { + expect(getFont('English text')).toBe('Roboto'); + expect(getFont('')).toBe('Roboto'); + expect(getFont(undefined!)).toBe('Roboto'); + }); +}); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/pdf/get_font.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/pdf/get_font.ts index e27489b5d66bc..4997d37327102 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/pdf/get_font.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/pdf/get_font.ts @@ -5,16 +5,11 @@ * 2.0. */ -// @ts-ignore: no module definition -import xRegExp from 'xregexp'; - export function getFont(text: string) { - // Once unicode regex scripts are fully supported we should be able to get rid of the dependency - // on xRegExp library. See https://github.com/tc39/proposal-regexp-unicode-property-escapes - // for more information. We are matching Han characters which is one of the supported unicode scripts + // We are matching Han characters which is one of the supported unicode scripts // (you can see the full list of supported scripts here: http://www.unicode.org/standard/supported.html). // This will match Chinese, Japanese, Korean and some other Asian languages. - const isCKJ = xRegExp('\\p{Han}').test(text, 'g'); + const isCKJ = /\p{Script=Han}/gu.test(text); if (isCKJ) { return 'noto-cjk'; } else { diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 34fcfa5f0befd..31b4cef1a9d45 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', @@ -166,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/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index aade8be4f503f..d97820f010a80 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/detection_engine/schemas/request/rule_schemas.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts index fb29e37a53fdb..3bae9551f4df7 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 cf07389e207b3..8dc7427ed0933 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> { 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 a430c429d54dc..fa3029405dc22 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 de88409549f8a..319933be5c79e 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/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 df6543ddbce90..c71108d58d980 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/cases/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts index 5a2cf5408b04d..64ce6be9ec457 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/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts index a69f808001800..bc52be678347a 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 d93ed7a0e97a5..3c188345111c8 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/cypress/screens/all_cases.ts b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts index 06d1a9fca91c6..e9c5ff89dd8c4 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/app/search/index.test.ts b/x-pack/plugins/security_solution/public/app/search/index.test.ts new file mode 100644 index 0000000000000..d6c36e89558d0 --- /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 0000000000000..110356269e891 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/search/index.ts @@ -0,0 +1,242 @@ +/* + * 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: [ + { + id: 'create', + title: i18n.translate('xpack.securitySolution.search.cases.create', { + defaultMessage: 'Create New Case', + }), + path: '/create', + }, + ], + 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 4d1c091fdaa5f..95e64fe37d333 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/cases/components/add_comment/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx index c447e00cbb94f..d02f7e0ee0961 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 01b86a989e022..c94ef75523e2c 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 ae2d987955993..66563deae5422 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,8 +9,9 @@ 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 { statuses } from '../status'; import * as i18n from './translations'; interface GetActions { @@ -19,46 +20,74 @@ interface GetActions { deleteCaseOnClick: (deleteCase: Case) => void; } +const hasSubCases = (subCases: SubCase[] | null | undefined) => + subCases != null && subCases?.length > 0; + 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', - }, - 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', - }, -]; +}: 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.open, + caseId: theCase.id, + version: theCase.version, + }), + 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['in-progress'], + caseId: theCase.id, + version: theCase.version, + }), + 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/columns.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx index 38f1b343670c8..47db362c7b4bf 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 0000000000000..bb4bd0f98949d --- /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 0000000000000..1ab36d3c67225 --- /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 318143426af58..a145bdf117813 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, }, ], }, @@ -278,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({ @@ -302,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({ @@ -314,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, @@ -392,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, @@ -542,6 +588,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 001251a8a71ae..ce0fea07bf473 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 bcc5df6c39e5b..3b27ef25eda14 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/bulk_actions/index.tsx b/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx index f9722b3903b12..ec3b391cdcbfe 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, { - 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 6b92e414675e2..3dece29e64ac5 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 dc0ef9ad026a4..7a5f6647a8dcf 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 0eaa867077a4a..e42431e55ee29 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 2c2ebc5360799..0bd37fa18281a 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 656257f2b36c4..d5c90bd09a6db 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 348e488a36a8d..1c786bade9753 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 c9553455f687d..731e94a17d923 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/flyout.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx new file mode 100644 index 0000000000000..842fe9e00ab39 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx @@ -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. + */ + +/* eslint-disable react/display-name */ +import React, { ReactNode } from 'react'; +import { mount } from 'enzyme'; + +import '../../../common/mock/match_media'; +import { CreateCaseFlyout } from './flyout'; +import { TestProviders } from '../../../common/mock'; + +jest.mock('../create/form_context', () => { + return { + FormContext: ({ + children, + onSuccess, + }: { + children: ReactNode; + onSuccess: ({ id }: { id: string }) => void; + }) => { + return ( + <> + + {children} + + ); + }, + }; +}); + +jest.mock('../create/form', () => { + return { + CreateCaseForm: () => { + return <>{'form'}; + }, + }; +}); + +jest.mock('../create/submit_button', () => { + return { + SubmitCaseButton: () => { + return <>{'Submit'}; + }, + }; +}); + +const onCloseFlyout = jest.fn(); +const onCaseCreated = jest.fn(); +const defaultProps = { + onCloseFlyout, + onCaseCreated, +}; + +describe('CreateCaseFlyout', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('renders', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj='create-case-flyout']`).exists()).toBeTruthy(); + }); + + it('Closing modal calls onCloseCaseModal', () => { + const wrapper = mount( + + + + ); + + wrapper.find('.euiFlyout__closeButton').first().simulate('click'); + expect(onCloseFlyout).toBeCalled(); + }); + + it('pass the correct props to FormContext component', () => { + const wrapper = mount( + + + + ); + + const props = wrapper.find('FormContext').props(); + expect(props).toEqual( + expect.objectContaining({ + onSuccess: onCaseCreated, + }) + ); + }); + + it('onSuccess called when creating a case', () => { + const wrapper = mount( + + + + ); + + wrapper.find(`[data-test-subj='form-context-on-success']`).first().simulate('click'); + expect(onCaseCreated).toHaveBeenCalledWith({ id: 'case-id' }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx new file mode 100644 index 0000000000000..cb3436f6ba3bc --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx @@ -0,0 +1,69 @@ +/* + * 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, { memo } from 'react'; +import styled from 'styled-components'; +import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui'; + +import { FormContext } from '../create/form_context'; +import { CreateCaseForm } from '../create/form'; +import { SubmitCaseButton } from '../create/submit_button'; +import { Case } from '../../containers/types'; +import * as i18n from '../../translations'; + +export interface CreateCaseModalProps { + onCloseFlyout: () => void; + onCaseCreated: (theCase: Case) => void; +} + +const Container = styled.div` + ${({ theme }) => ` + margin-top: ${theme.eui.euiSize}; + text-align: right; + `} +`; + +const StyledFlyout = styled(EuiFlyout)` + ${({ theme }) => ` + z-index: ${theme.eui.euiZModal}; + `} +`; + +// Adding bottom padding because timeline's +// bottom bar gonna hide the submit button. +const FormWrapper = styled.div` + padding-bottom: 50px; +`; + +const CreateCaseFlyoutComponent: React.FC = ({ + onCaseCreated, + onCloseFlyout, +}) => { + return ( + + + +

{i18n.CREATE_TITLE}

+
+
+ + + + + + + + + + +
+ ); +}; + +export const CreateCaseFlyout = memo(CreateCaseFlyoutComponent); + +CreateCaseFlyout.displayName = 'CreateCaseFlyout'; 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 cc38e07cf49e4..83b8870ab597d 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 909b49940e189..81a7fe9cd9387 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/status/button.test.tsx b/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx index aa99543b49d2a..ab30fe2979b9e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx @@ -42,7 +42,7 @@ describe('StatusActionButton', () => { 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 2ec0ccd245b1e..4ee69766fe128 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 ( { 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 6067f3fd7b15a..aa9cec2d6b5b1 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 @@ -25,10 +25,11 @@ import { APP_ID } from '../../../../common/constants'; import { useKibana } from '../../../common/lib/kibana'; import { getCaseDetailsUrl } from '../../../common/components/link_to'; import { SecurityPageName } from '../../../app/types'; -import { useCreateCaseModal } from '../use_create_case_modal'; import { useAllCasesModal } from '../use_all_cases_modal'; import { createUpdateSuccessToaster } from './helpers'; import * as i18n from './translations'; +import { useControl } from '../../../common/hooks/use_control'; +import { CreateCaseFlyout } from '../create/flyout'; interface AddToCaseActionProps { ariaLabel?: string; @@ -43,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(); @@ -61,29 +63,36 @@ const AddToCaseActionComponent: React.FC = ({ [navigateToApp] ); + const { + isControlOpen: isCreateCaseFlyoutOpen, + openControl: openCaseFlyoutOpen, + closeControl: closeCaseFlyoutOpen, + } = useControl(); + const attachAlertToCase = useCallback( (theCase: Case) => { - postComment( - theCase.id, - { + closeCaseFlyoutOpen(); + 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), - }) - ); + }), + }); }, - [postComment, eventId, eventIndex, dispatchToaster, onViewCaseClick] + [closeCaseFlyoutOpen, postComment, eventId, eventIndex, rule, dispatchToaster, onViewCaseClick] ); - const { modal: createCaseModal, openModal: openCreateCaseModal } = useCreateCaseModal({ - onCaseCreated: attachAlertToCase, - }); - const onCaseClicked = useCallback( (theCase) => { /** @@ -92,13 +101,13 @@ const AddToCaseActionComponent: React.FC = ({ * We gonna open the create case modal. */ if (theCase == null) { - openCreateCaseModal(); + openCaseFlyoutOpen(); return; } attachAlertToCase(theCase); }, - [attachAlertToCase, openCreateCaseModal] + [attachAlertToCase, openCaseFlyoutOpen] ); const { modal: allCasesModal, openModal: openAllCaseModal } = useAllCasesModal({ @@ -107,8 +116,8 @@ const AddToCaseActionComponent: React.FC = ({ const addNewCaseClick = useCallback(() => { closePopover(); - openCreateCaseModal(); - }, [openCreateCaseModal, closePopover]); + openCaseFlyoutOpen(); + }, [openCaseFlyoutOpen, closePopover]); const addExistingCaseClick = useCallback(() => { closePopover(); @@ -173,7 +182,9 @@ const AddToCaseActionComponent: React.FC = ({ - {createCaseModal} + {isCreateCaseFlyoutOpen && ( + + )} {allCasesModal} ); 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 8dd5080666cb3..2806e358fceee 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 86313ebcb3bfa..3dc852a19e73f 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 3b81fc0afccf3..2a9f99465251b 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 ede216d562f11..46f36615b1a4e 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 f0ff145a269ff..228945bacf8a4 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 183116167f84a..2a604b7c54d6b 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 4bd4734e30671..ff4e151197464 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 5d619a39d0e79..789a6eb68e0fc 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 ea4994d1c8098..4f5ce00806417 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 00a45aadd2ae0..c87e210b42bc0 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 80d4816bedd53..d8692da986cbe 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/translations.ts b/x-pack/plugins/security_solution/public/cases/containers/translations.ts index 75939b46b1f77..c79b897ba43a1 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/translations.ts @@ -53,7 +53,20 @@ export const REOPENED_CASES = ({ }) => 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/types.ts b/x-pack/plugins/security_solution/public/cases/containers/types.ts index 30ea834443468..d2931a790bd79 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_bulk_update_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx index 5fd181a4bbd41..0fe45aaab799b 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/containers/use_delete_cases.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx index b777b16b1c0c1..923c20dcf8ebd 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 45827a4bebff8..1c4476e3cb2b7 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 8ebd46e64296f..12e5f6643351f 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 f9d4454f63ffb..42cd0deafa048 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 f2bd9d3f41f3c..8fc8053c14f70 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 62560244fe9c8..0adf2cc0bf92a 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 b2b919ae1422b..23a23caeb71bd 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 d7d98879459fe..9ff266ad9c988 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 6222d993bb798..e36b21823310e 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 701ecdf8580f0..edb84db89b878 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 32c94e593665f..314bdc9bfd117 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/cases/translations.ts b/x-pack/plugins/security_solution/public/cases/translations.ts index 156cb91994468..caaa1f6e248ea 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', }); 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 e388b485ba89c..af76a79f0e330 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 1a66dd2f27cc2..507fd51a90486 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 3593fe3d05491..6108a21ce5624 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/common/components/link_to/redirect_to_case.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_case.tsx index 7a7da6f89306c..82d8aac904e9b 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 6b4148db2b1ee..8e2f57a1a597c 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/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts index 0da881d2c6ea4..b993bcda56b8e 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/common/hooks/use_control.test.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_control.test.tsx new file mode 100644 index 0000000000000..953f39fcf2372 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_control.test.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { useControl, UseControlsReturn } from './use_control'; + +describe('useControl', () => { + it('init', async () => { + const { result } = renderHook<{}, UseControlsReturn>(() => useControl()); + expect(result.current.isControlOpen).toBe(false); + }); + + it('should open the control', async () => { + const { result } = renderHook<{}, UseControlsReturn>(() => useControl()); + + act(() => { + result.current.openControl(); + }); + + expect(result.current.isControlOpen).toBe(true); + }); + + it('should close the control', async () => { + const { result } = renderHook<{}, UseControlsReturn>(() => useControl()); + + act(() => { + result.current.openControl(); + }); + + expect(result.current.isControlOpen).toBe(true); + + act(() => { + result.current.closeControl(); + }); + + expect(result.current.isControlOpen).toBe(false); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_control.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_control.tsx new file mode 100644 index 0000000000000..37d7f4ff79986 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_control.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 { useState, useCallback } from 'react'; + +export interface UseControlsReturn { + isControlOpen: boolean; + openControl: () => void; + closeControl: () => void; +} + +export const useControl = (): UseControlsReturn => { + const [isControlOpen, setIsControlOpen] = useState(false); + const openControl = useCallback(() => setIsControlOpen(true), []); + const closeControl = useCallback(() => setIsControlOpen(false), []); + + return { isControlOpen, openControl, closeControl }; +}; 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 143c39daace66..7d577659d66e2 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 9d38e2b369fa1..9f5ab7be8a117 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,11 @@ 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) => { return { @@ -46,7 +50,7 @@ export const getUpdateAlertsQuery = (eventIds: Readonly) => { bool: { filter: { terms: { - _id: [...eventIds], + _id: eventIds, }, }, }, @@ -102,17 +106,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 +147,82 @@ 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; - - if (!dataProviderValue) { - return []; - } + const thresholdEcsData: Ecs[] = Array.isArray(ecsData) ? ecsData : [ecsData]; + return thresholdEcsData.reduce((outerAcc, thresholdData) => { + const threshold = thresholdData.signal?.rule?.threshold as string[]; - const aggregationFieldId = aggregationField.replace('.', '-'); + let aggField: string[] = []; + let thresholdResult: { + terms?: Array<{ + field?: string; + value: string; + }>; + count: number; + }; - 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: ':', - }, - }, - ]; + 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 aggregationFields = Array.isArray(aggField) ? aggField : [aggField]; + + return [ + ...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; + } + }, []), + ]; + }, []); }; export const isEqlRuleWithGroupId = (ecsData: Ecs) => @@ -169,20 +233,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 +453,7 @@ export const sendAlertToTimelineAction = async ({ ...timelineDefaults, description: `_id: ${ecsData._id}`, filters: getFiltersFromRule(ecsData.signal?.rule?.filters as string[]), - dataProviders: [...getThresholdAggregationDataProvider(ecsData, nonEcsData)], + dataProviders: getThresholdAggregationDataProvider(ecsData, nonEcsData), id: TimelineId.active, indexNames: [], dateRange: { @@ -301,36 +479,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 +499,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/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 9236700c2b2e2..b2e5638ff120e 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 ( + + ); +}; 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 d6813fdef8e54..2f0fee980c218 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/components/rules/query_preview/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx index e954f961e7336..bb87242d9bf10 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 6a11dcf316de3..377259fc9b212 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 ff90978dbb53f..d1a9e5c5f768f 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 84206b51272df..2d301bf96122d 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/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 f3669e128ac6e..0000000000000 --- 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 677e6de0ff485..42a6cb8bed1d7 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/components/rules/rule_switch/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx index 4f87f9332475b..268ffe620ad4e 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/components/rules/step_about_rule/default_value.ts b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts index f73b2ccfb02ae..08feb5f2e5166 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts @@ -29,7 +29,6 @@ export const stepAboutDefaultValue: AboutStepRule = { license: '', ruleNameOverride: '', tags: [], - threatIndicatorPath: '', timestampOverride: '', threat: threatDefault, note: '', diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx index 25295a823ea66..60647bbf9f4a4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx @@ -41,6 +41,7 @@ import { RiskScoreField } from '../risk_score_mapping'; import { AutocompleteField } from '../autocomplete_field'; import { useFetchIndex } from '../../../../common/containers/source'; import { isThreatMatchRule } from '../../../../../common/detection_engine/utils'; +import { DEFAULT_INDICATOR_PATH } from '../../../../../common/constants'; const CommonUseField = getUseField({ component: Field }); @@ -309,7 +310,7 @@ const StepAboutRuleComponent: FC = ({ euiFieldProps: { fullWidth: true, disabled: isLoading, - placeholder: 'threat.indicator', + placeholder: DEFAULT_INDICATOR_PATH, }, }} /> 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 5613d9bc1b458..4c7a34dbdf080 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 18f5008ff05f7..a5352ede83d51 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 d473a83a9e35e..287c99dce3e60 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/containers/detection_engine/alerts/use_query.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_query.tsx index 475160b787272..8557e1082c1cb 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/public/detections/containers/detection_engine/rules/index.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts index 751cde64bb87d..8128eb045f759 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/apm/common/utils/range_filter.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/index.ts similarity index 60% rename from x-pack/plugins/apm/common/utils/range_filter.ts rename to x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/index.ts index 8d5b7d5e1beb1..a05349fa4fa3a 100644 --- a/x-pack/plugins/apm/common/utils/range_filter.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/index.ts @@ -5,12 +5,7 @@ * 2.0. */ -export function rangeFilter(start: number, end: number) { - return { - '@timestamp': { - gte: start, - lte: end, - format: 'epoch_millis', - }, - }; -} +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 0000000000000..77c327c9f7939 --- /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 20e4e5f747349..1a45c60dba58a 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 0000000000000..edcf4f6395d89 --- /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 5d226e8152596..4532d3427375b 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 02784333992be..f3c90ae12ae33 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 0000000000000..f31b2894301ba --- /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/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts index 9853b8c6d34bc..8bdbb1a74c73a 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/all/actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx index 44b0476501a5b..3b1f9e620127d 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 ee19a0b535075..d3e055a695d61 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 d110f2d52b3c5..d2488bd3d043c 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 a784d23ab0015..7f9061b6abc83 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 60798f10a4c58..0000000000000 --- 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 04bf3c544030a..a40833d8d14ac 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 ( 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 7c447214cfdeb..12e6d276c18d8 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/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index c4dc9b62c74cd..4e225917f076d 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} > 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 111eb8a5594a8..29d1512030e74 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: [], @@ -116,7 +118,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' }; @@ -214,6 +215,8 @@ describe('rule helpers', () => { threshold: { field: [], value: '100', + cardinality_field: [], + cardinality_value: '0', }, threatIndex: [], threatMapping: [], @@ -256,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 d37c2d9141f5d..7c3930bb21d9a 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}`, }, }); @@ -180,7 +190,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/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index 94fdcc4069fc2..668ca556539ad 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/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 b69614e73e7f6..540191ac63b6c 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 1394e2660a5ae..da574dfa4ea44 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/public/management/pages/policy/models/advanced_policy_schema.ts b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts index 66fed92b5a26b..b5bf8594e7a8d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts @@ -595,4 +595,35 @@ export const AdvancedPolicySchema: AdvancedPolicySchemaType[] = [ } ), }, + { + key: 'windows.advanced.alerts.cloud_lookup', + first_supported_version: '7.12', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.alerts.cloud_lookup', + { + defaultMessage: + "A value of 'false' disables cloud lookup for Windows alerts. Default: true.", + } + ), + }, + { + key: 'mac.advanced.alerts.cloud_lookup', + first_supported_version: '7.12', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.alerts.cloud_lookup', + { + defaultMessage: "A value of 'false' disables cloud lookup for Mac alerts. Default: true.", + } + ), + }, + { + key: 'windows.advanced.ransomware.mbr', + first_supported_version: '7.12', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.ransomware.mbr', + { + defaultMessage: "A value of 'false' disables Ransomware MBR protection. Default: true.", + } + ), + }, ]; 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 3e18c7a01c808..1a007cd7f0f56 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/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 1b7b7059cbafc..6aaad4a157191 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,13 @@ 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; /** * Lazily instantiated subPlugins. @@ -184,6 +191,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { detections: subPlugin } = await this.subPlugins(); @@ -206,6 +214,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { hosts: subPlugin } = await this.subPlugins(); @@ -227,6 +236,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { network: subPlugin } = await this.subPlugins(); @@ -248,6 +258,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { timelines: subPlugin } = await this.subPlugins(); @@ -269,6 +280,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { cases: subPlugin } = await this.subPlugins(); @@ -290,6 +302,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { management: managementSubPlugin } = await this.subPlugins(); @@ -356,11 +369,31 @@ 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); + registerSearchLinks(SecurityPageName.case, this.caseUpdater$, currentLicense.type); + } + }); + } return {}; } public stop() { + if (this.licensingSubscription !== null) { + this.licensingSubscription.unsubscribe(); + } return {}; } diff --git a/x-pack/plugins/security_solution/public/resolver/view/resolver_loading_state.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/resolver_loading_state.test.tsx index a854884d2e340..4a2056963ee7f 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/resolver_loading_state.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/resolver_loading_state.test.tsx @@ -130,18 +130,18 @@ describe('Resolver: data loading and resolution states', () => { }); }); - it('should display a resolver graph with 0 nodes', async () => { + it('should display a message informing the user why no nodes are present', async () => { await expect( simulator.map(() => ({ resolverGraphLoading: simulator.testSubject('resolver:graph:loading').length, resolverGraphError: simulator.testSubject('resolver:graph:error').length, - resolverTree: simulator.testSubject('resolver:graph').length, + resolverEmptyMessage: simulator.testSubject('resolver:no-process-events').length, resolverGraphNodes: simulator.testSubject('resolver:node').length, })) ).toYieldEqualTo({ resolverGraphLoading: 0, resolverGraphError: 0, - resolverTree: 1, + resolverEmptyMessage: 1, resolverGraphNodes: 0, }); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/resolver_no_process_events.tsx b/x-pack/plugins/security_solution/public/resolver/view/resolver_no_process_events.tsx new file mode 100644 index 0000000000000..7159b0bda468b --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/resolver_no_process_events.tsx @@ -0,0 +1,55 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; +import { EuiCodeBlock, EuiFlexGroup, EuiTitle, EuiSpacer, EuiText } from '@elastic/eui'; + +const StyledEuiCodeBlock = styled(EuiCodeBlock)` + align-self: flex-start; + display: inline-block; +`; + +const StyledEuiFlexGroup = styled(EuiFlexGroup)` + max-width: 600px; + margin: 60px auto 0; +`; + +export const ResolverNoProcessEvents = () => ( + + +

+ {i18n.translate('xpack.securitySolution.resolver.noProcessEvents.title', { + defaultMessage: 'No Process Events Found', + })} +

+
+ + + {i18n.translate('xpack.securitySolution.resolver.noProcessEvents.timeRange', { + defaultMessage: ` + The Analyze Event tool creates graphs based on process events. + If the analyzed event does not have an associated process in the current time range, + or stored in Elasticsearch within any time range, a graph will not be created. + You can check for associated processes by expanding your time range. + `, + })} + + + + {i18n.translate('xpack.securitySolution.resolver.noProcessEvents.eventCategory', { + defaultMessage: `You may also add the below to your timeline query to check for process events. + If none are listed, a graph cannot be created from events found in that query.`, + })} + + + + {"event.category: 'process'"} + +
+); diff --git a/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx b/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx index 6fbb81a5fe0da..7b528bfa4d99d 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx @@ -7,7 +7,7 @@ /* eslint-disable react/display-name */ -import React, { useContext, useCallback } from 'react'; +import React, { useContext, useCallback, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { EuiLoadingSpinner } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -26,6 +26,7 @@ import { ResolverProps, ResolverState } from '../types'; import { PanelRouter } from './panels'; import { useColors } from './use_colors'; import { useSyncSelectedNode } from './use_sync_selected_node'; +import { ResolverNoProcessEvents } from './resolver_no_process_events'; /** * The highest level connected Resolver component. Needs a `Provider` in its ancestry to work. @@ -96,6 +97,10 @@ export const ResolverWithoutProviders = React.memo( const activeDescendantId = useSelector(selectors.ariaActiveDescendant); const colorMap = useColors(); + const noProcessEventsFound = useMemo(() => processNodePositions.size < 1, [ + processNodePositions, + ]); + return ( {isLoading ? ( @@ -112,6 +117,8 @@ export const ResolverWithoutProviders = React.memo( /> + ) : noProcessEventsFound ? ( + ) : ( <> = ({ 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/side_panel/host_details/expandable_host.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx index 4e101e29bb484..8fce9a186bbd4 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 b12b575681acf..19f6e2c9652f9 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 45b10d635195b..4191badd6b03f 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/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index 32a01654bf9af..dd701aa284997 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]); diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 7a9e9f07b2062..e88077679e1b6 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; 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 909264c57067b..22dba81e5c8e6 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/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 6cf46e35595de..8084067b3a6d2 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 69ba61dea3228..9c28d065b322d 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 323ad5c0f446b..352712e38f42d 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 9ca5a3da5ae21..259bcd51aeb3e 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 4923e42d16d9c..19348062b10f1 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 9c9fcc559bea7..2fd3aaa0d8a57 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 50861dba7f3fb..8f90e1162546b 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 72d52a9727320..3d740f8b7064f 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 7af172d90eb30..33195c7fcbecc 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 28f473795299a..fac13a6d358dd 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 09c9f83f95622..a2d8700076c23 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 af235ea2022cf..ef4f29067b0c5 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 cd16caf11482b..b22751e35c053 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 8353cc06972e2..3b973f42bbca5 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 590c6b0814067..b6458b73e8015 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 } 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 6177fc4cd4661..977b38e59f856 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 ca8fa821ce032..362c368881b37 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 cfbcfe5a04e59..78ff0e8e1e5dd 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 713178345361d..56d71048bb81b 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 dd9e1e97a2b73..29fd189bb34f3 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 6144f1f4b3823..7796346e9876d 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 da7ee8796afbf..710a925fe315b 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 ecb36a8b050d9..98c9dd41d179c 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/single_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts index e33ee4d5762ab..15261ab5fad01 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/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 fbea610428bd0..9c58bba296ad3 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/threat_mapping/build_threat_enrichment.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_enrichment.ts index 4f38f2db9230a..cdbafe692c630 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 fada314116871..f98f0c88a2dfa 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 c5b032207f1c5..761a58224fac5 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, + }, + }; }; 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 dbad1d12d2be6..8ed5929e72504 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 0000000000000..ed9aa9a5ba698 --- /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 96224be181f47..e1727c0361afc 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 f7ac0425b2f2e..e5ca1f6a60456 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 f7e1eb7622779..7888bb6deaab7 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 f6bd5c8a325be..58bf22be97bf8 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 }), @@ -851,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 @@ -859,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 9d030e0d59bc6..a8616dc1c57d1 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/security_solution/factory/hosts/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts index 2a669fcd9f2e9..5575b4fb487e7 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 9388807f25a6f..5cee547a6b365 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 0cad31bffb2a1..a6d5dcdf022b5 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 d416586f59cf7..d0405d829b83d 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 fd0c92ee2bdf5..fee9a49e42c48 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 be9b24308f1f6..fe2cb96144e22 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 d601a5905dd6e..21876b9aad11b 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/all/constants.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts index 5ed324496e609..15d0e2d5494b8 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 5c8f7f87a6c49..10bb606dc2387 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: [], 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 ab37c730c604d..3b02e5621ed1a 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 { }); }); - test('getPreviewTransformRequestBody() with missing_buckets config', () => { - const query = getPivotQuery('the-query'); - const request = getPreviewTransformRequestBody('the-index-pattern-title', query, { + test('getMissingBucketConfig()', () => { + expect(getMissingBucketConfig(groupByTerms)).toEqual({}); + expect(getMissingBucketConfig({ ...groupByTerms, ...{ missing_bucket: true } })).toEqual({ + missing_bucket: true, + }); + expect(getMissingBucketConfig({ ...groupByTerms, ...{ missing_bucket: false } })).toEqual({ + missing_bucket: false, + }); + }); + + test('getRequestPayload()', () => { + expect(getRequestPayload([], [groupByTerms])).toEqual({ pivot: { - aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } }, + aggregations: {}, group_by: { - 'the-group-by-agg-name': { terms: { field: 'the-group-by-field', missing_bucket: true } }, + 'the-group-by-agg-name': { + terms: { + field: 'the-group-by-field', + }, + }, + }, + }, + }); + expect(getRequestPayload([], [{ ...groupByTerms, ...{ missing_bucket: true } }])).toEqual({ + pivot: { + aggregations: {}, + group_by: { + 'the-group-by-agg-name': { + terms: { + field: 'the-group-by-field', + missing_bucket: true, + }, + }, }, }, }); + expect(getRequestPayload([], [{ ...groupByTerms, ...{ missing_bucket: false } }])).toEqual({ + pivot: { + aggregations: {}, + group_by: { + 'the-group-by-agg-name': { + terms: { + field: 'the-group-by-field', + missing_bucket: false, + }, + }, + }, + }, + }); + }); + + test('getPreviewTransformRequestBody() with missing_buckets config', () => { + const query = getPivotQuery('the-query'); + const request = getPreviewTransformRequestBody( + 'the-index-pattern-title', + query, + getRequestPayload([aggsAvg], [{ ...groupByTerms, ...{ missing_bucket: true } }]) + ); expect(request).toEqual({ pivot: { diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts index e4cfd0a874f0f..fbd4be706b539 100644 --- a/x-pack/plugins/transform/public/app/common/request.ts +++ b/x-pack/plugins/transform/public/app/common/request.ts @@ -11,17 +11,30 @@ import { HttpFetchError } from '../../../../../../src/core/public'; import type { IndexPattern } from '../../../../../../src/plugins/data/public'; import type { + PivotTransformPreviewRequestSchema, PostTransformsPreviewRequestSchema, PutTransformsLatestRequestSchema, PutTransformsPivotRequestSchema, PutTransformsRequestSchema, } from '../../../common/api_schemas/transforms'; +import { DateHistogramAgg, HistogramAgg, TermsAgg } from '../../../common/types/pivot_group_by'; import type { SavedSearchQuery } from '../hooks/use_search_items'; import type { StepDefineExposedState } from '../sections/create_transform/components/step_define'; import type { StepDetailsExposedState } from '../sections/create_transform/components/step_details'; import { isPopulatedObject } from './utils/object_utils'; +import { + getEsAggFromAggConfig, + getEsAggFromGroupByConfig, + isGroupByDateHistogram, + isGroupByHistogram, + isGroupByTerms, + GroupByConfigWithUiSupport, + PivotAggsConfig, + PivotGroupByConfig, +} from './'; + export interface SimpleQuery { query_string: { query: string; @@ -81,6 +94,66 @@ export function getCombinedRuntimeMappings( return undefined; } +export const getMissingBucketConfig = ( + g: GroupByConfigWithUiSupport +): { missing_bucket?: boolean } => { + return g.missing_bucket !== undefined ? { missing_bucket: g.missing_bucket } : {}; +}; + +export const getRequestPayload = ( + pivotAggsArr: PivotAggsConfig[], + pivotGroupByArr: PivotGroupByConfig[] +) => { + const request = { + pivot: { + group_by: {}, + aggregations: {}, + } as PivotTransformPreviewRequestSchema['pivot'], + }; + + pivotGroupByArr.forEach((g) => { + if (isGroupByTerms(g)) { + const termsAgg: TermsAgg = { + terms: { + field: g.field, + ...getMissingBucketConfig(g), + }, + }; + request.pivot.group_by[g.aggName] = termsAgg; + } else if (isGroupByHistogram(g)) { + const histogramAgg: HistogramAgg = { + histogram: { + field: g.field, + interval: g.interval, + ...getMissingBucketConfig(g), + }, + }; + request.pivot.group_by[g.aggName] = histogramAgg; + } else if (isGroupByDateHistogram(g)) { + const dateHistogramAgg: DateHistogramAgg = { + date_histogram: { + field: g.field, + calendar_interval: g.calendar_interval, + ...getMissingBucketConfig(g), + }, + }; + request.pivot.group_by[g.aggName] = dateHistogramAgg; + } else { + request.pivot.group_by[g.aggName] = getEsAggFromGroupByConfig(g); + } + }); + + pivotAggsArr.forEach((agg) => { + const result = getEsAggFromAggConfig(agg); + if (result === null) { + return; + } + request.pivot.aggregations[agg.aggName] = result; + }); + + return request; +}; + export function getPreviewTransformRequestBody( indexPatternTitle: IndexPattern['title'], query: PivotQuery, 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 ad11f092e8e57..ded14a2c0e69e 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 ba53bb2da5716..9c9fb59eea4b1 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 036342ef16344..baf0bbd655925 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 34a463c0f71b8..da74553aa89c2 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())}> diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts index cdba7a3f5482c..6c9293a6d13cf 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts @@ -40,7 +40,7 @@ type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; export interface RuntimeField { type: RuntimeType; - script: + script?: | string | { source: string; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.test.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.test.ts deleted file mode 100644 index fafedf53dd9b7..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.test.ts +++ /dev/null @@ -1,28 +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 { getMissingBucketConfig } from './use_pivot_config'; -import { PIVOT_SUPPORTED_GROUP_BY_AGGS, PivotGroupByConfig } from '../../../../../common'; - -const groupByTerms: PivotGroupByConfig = { - agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS, - field: 'the-group-by-field', - aggName: 'the-group-by-agg-name', - dropDownName: 'the-group-by-drop-down-name', -}; - -describe('usePivotConfig', () => { - test('getMissingBucketConfig()', () => { - expect(getMissingBucketConfig(groupByTerms)).toEqual({}); - expect(getMissingBucketConfig({ ...groupByTerms, ...{ missing_bucket: true } })).toEqual({ - missing_bucket: true, - }); - expect(getMissingBucketConfig({ ...groupByTerms, ...{ missing_bucket: false } })).toEqual({ - missing_bucket: false, - }); - }); -}); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts index a02d3bafac984..4bd8f5cea6092 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts @@ -13,13 +13,8 @@ import { dictionaryToArray } from '../../../../../../../common/types/common'; import { useToastNotifications } from '../../../../../app_dependencies'; import { + getRequestPayload, DropDownLabel, - getEsAggFromAggConfig, - getEsAggFromGroupByConfig, - GroupByConfigWithUiSupport, - isGroupByDateHistogram, - isGroupByHistogram, - isGroupByTerms, PivotAggsConfig, PivotAggsConfigDict, PivotGroupByConfig, @@ -32,12 +27,6 @@ import { } from '../common'; import { StepDefineFormProps } from '../step_define_form'; import { isPivotAggsWithExtendedForm } from '../../../../../common/pivot_aggs'; -import { - DateHistogramAgg, - HistogramAgg, - TermsAgg, -} from '../../../../../../../common/types/pivot_group_by'; -import { PivotTransformPreviewRequestSchema } from '../../../../../../../common/api_schemas/transforms'; import { TransformPivotConfig } from '../../../../../../../common/types/transform'; /** @@ -102,12 +91,6 @@ function getRootAggregation(item: PivotAggsConfig) { return rootItem; } -export const getMissingBucketConfig = ( - g: GroupByConfigWithUiSupport -): { missing_bucket?: boolean } => { - return g.missing_bucket !== undefined ? { missing_bucket: g.missing_bucket } : {}; -}; - export const usePivotConfig = ( defaults: StepDefineExposedState, indexPattern: StepDefineFormProps['searchItems']['indexPattern'] @@ -315,56 +298,10 @@ export const usePivotConfig = ( const pivotAggsArr = useMemo(() => dictionaryToArray(aggList), [aggList]); const pivotGroupByArr = useMemo(() => dictionaryToArray(groupByList), [groupByList]); - const requestPayload = useMemo(() => { - const request = { - pivot: { - group_by: {}, - aggregations: {}, - } as PivotTransformPreviewRequestSchema['pivot'], - }; - - pivotGroupByArr.forEach((g) => { - if (isGroupByTerms(g)) { - const termsAgg: TermsAgg = { - terms: { - field: g.field, - }, - ...getMissingBucketConfig(g), - }; - request.pivot.group_by[g.aggName] = termsAgg; - } else if (isGroupByHistogram(g)) { - const histogramAgg: HistogramAgg = { - histogram: { - field: g.field, - interval: g.interval, - }, - ...getMissingBucketConfig(g), - }; - request.pivot.group_by[g.aggName] = histogramAgg; - } else if (isGroupByDateHistogram(g)) { - const dateHistogramAgg: DateHistogramAgg = { - date_histogram: { - field: g.field, - calendar_interval: g.calendar_interval, - }, - ...getMissingBucketConfig(g), - }; - request.pivot.group_by[g.aggName] = dateHistogramAgg; - } else { - request.pivot.group_by[g.aggName] = getEsAggFromGroupByConfig(g); - } - }); - - pivotAggsArr.forEach((agg) => { - const result = getEsAggFromAggConfig(agg); - if (result === null) { - return; - } - request.pivot.aggregations[agg.aggName] = result; - }); - - return request; - }, [pivotAggsArr, pivotGroupByArr]); + const requestPayload = useMemo(() => getRequestPayload(pivotAggsArr, pivotGroupByArr), [ + pivotAggsArr, + pivotGroupByArr, + ]); const validationStatus = useMemo(() => { return validatePivotConfig(requestPayload.pivot); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3196bcc6031c9..c561339d1a667 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} で始まるフィールド名はサポートされません", @@ -5245,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": "トラフィック", @@ -5258,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": "トランザクション", @@ -17251,7 +17246,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": "タグ", @@ -18876,8 +18870,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 7292769cdbd0f..ab09f6c1ec56e 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} 开头的字段名称", @@ -5254,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": "流量", @@ -5267,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": "事务", @@ -17294,7 +17289,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": "标签", @@ -18922,8 +18916,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/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx index 4efe0f866a549..2f776f3937b50 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx @@ -44,12 +44,8 @@ interface ReindexFlyoutState { currentFlyoutStep: ReindexFlyoutStep; } -// eslint-disable-next-line @typescript-eslint/naming-convention -const getOpenAndCloseIndexDocLink = ({ ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }: DocLinksStart) => ( - +const getOpenAndCloseIndexDocLink = (docLinks: DocLinksStart) => ( + {i18n.translate( 'xpack.upgradeAssistant.checkupTab.reindexing.flyout.openAndCloseDocumentation', { defaultMessage: 'documentation' } 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 b91b50666cf07..68b9ab0435aaa 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/x-pack/test/accessibility/apps/kibana_overview.ts b/x-pack/test/accessibility/apps/kibana_overview.ts index 068b600d2adf2..fe255a3413bd0 100644 --- a/x-pack/test/accessibility/apps/kibana_overview.ts +++ b/x-pack/test/accessibility/apps/kibana_overview.ts @@ -20,23 +20,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { - await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { - useActualUrl: true, - }); - await PageObjects.home.removeSampleDataSet('flights'); await esArchiver.unload('empty_kibana'); }); - it('Getting started view', async () => { - await a11y.testAppSnapshot(); - }); - - it('Overview view', async () => { - await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { - useActualUrl: true, - }); - await PageObjects.home.addSampleDataSet('flights'); - await PageObjects.common.navigateToApp('kibanaOverview'); + it('Kibana overview', async () => { await a11y.testAppSnapshot(); }); }); diff --git a/x-pack/test/accessibility/apps/uptime.ts b/x-pack/test/accessibility/apps/uptime.ts index d7a9cfc0d08b4..874ede0b13ee9 100644 --- a/x-pack/test/accessibility/apps/uptime.ts +++ b/x-pack/test/accessibility/apps/uptime.ts @@ -19,6 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const es = getService('es'); // FLAKY: https://github.com/elastic/kibana/issues/90555 + // Failing: See https://github.com/elastic/kibana/issues/90555 describe.skip('uptime', () => { before(async () => { await esArchiver.load('uptime/blank'); diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts index 24c46c1a1687e..bfd12c4eee6e7 100644 --- a/x-pack/test/accessibility/config.ts +++ b/x-pack/test/accessibility/config.ts @@ -18,6 +18,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [ require.resolve('./apps/login_page'), require.resolve('./apps/home'), + require.resolve('./apps/kibana_overview'), require.resolve('./apps/grok_debugger'), require.resolve('./apps/search_profiler'), require.resolve('./apps/uptime'), @@ -26,7 +27,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./apps/dashboard_edit_panel'), require.resolve('./apps/users'), require.resolve('./apps/roles'), - require.resolve('./apps/kibana_overview'), require.resolve('./apps/ingest_node_pipelines'), require.resolve('./apps/index_lifecycle_management'), require.resolve('./apps/ml'), diff --git a/x-pack/test/api_integration/apis/security_solution/hosts.ts b/x-pack/test/api_integration/apis/security_solution/hosts.ts index 3db60dc2bf93c..17f0bb58be0a9 100644 --- a/x-pack/test/api_integration/apis/security_solution/hosts.ts +++ b/x-pack/test/api_integration/apis/security_solution/hosts.ts @@ -142,24 +142,64 @@ export default function ({ getService }: FtrProviderContext) { expect(hostDetails).to.eql(expectedHostDetails); }); - it('Make sure that we get Last First Seen for a Host', async () => { + 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()); }); }); } diff --git a/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json b/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json index 90335d303a36d..f1cc32f33dd2c 100644 --- a/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json +++ b/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json @@ -977,7 +977,51 @@ }, "indices": 1, "plugins": {} - } + }, + "logstash": { + "count": 1, + "cluster_stats": { + "collection_types": { + "internal_collection": 1 + }, + "pipelines": { + "batch_size_min": 125, + "batch_size_max": 125, + "batch_size_avg": 125, + "batch_size_total": 125, + "count": 1, + "sources": { + "string": true + }, + "workers_avg": 4, + "workers_total": 4, + "workers_min": 4, + "workers_max": 4 + }, + "plugins": [ + { + "count": 1, + "name": "logstash-input-twitter" + }, + { + "count": 1, + "name": "logstash-output-stdout" + }, + { + "count": 1, + "name": "logstash-output-elasticsearch" + } + ], + "queues": { + "memory": 1 + } + }, + "versions": [ + { + "count": 1, + "version": "7.0.0-alpha1" + } + ]} }, "timestamp": "2017-08-15T22:10:52.642Z", "version": "7.0.0-alpha1", 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 1d00d54a3997b..4474d0996175b 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 db2b42323c4f6..45114dd506716 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/service_overview/instances.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts index 7c1b01d2715b6..cca40a6950007 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, 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 efd0b410cf8aa..3afaec653fcb3 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/traces/__snapshots__/top_traces.snap b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap index c8104b9858027..ad9eca1cc0900 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/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap index a384ca2c9364e..11c557fd02e38 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/distribution.ts b/x-pack/test/apm_api_integration/tests/transactions/distribution.ts index e46ec7a181fc0..56d5e217068a4 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", }, ] `); 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 3003c6f902f39..523139717b309 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 040e280e3157f..430392a32bfb8 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, 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 2250b481c3729..86b1c3031cbef 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 1ce011985d9e6..fb095c117cdfb 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 dcc49152e4db8..43d6be196da0d 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 01dd6ed5404c2..4812ead2c4c78 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 2f4fa1b30f564..f6fd2b1a6b3be 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, }; 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 a9120bde274f2..8d494724d9f76 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 4875e837556fc..684cbb6368ad9 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 diff --git a/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json b/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json index 7e7084698b788..319a2e95e47bb 100644 --- a/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json +++ b/x-pack/test/functional/es_archives/monitoring/multicluster/mappings.json @@ -602,25 +602,83 @@ "cluster_uuid": { "type": "keyword" }, - "logstash_state": { + "timestamp": { + "type": "date", + "format": "date_time" + }, + "interval_ms": { + "type": "long" + }, + "type": { + "type": "keyword" + }, + "source_node": { "properties": { - "pipeline": { + "uuid": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "transport_address": { + "type": "keyword" + }, + "ip": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "timestamp": { + "type": "date", + "format": "date_time" + } + } + }, + "logstash_stats": { + "type": "object", + "properties": { + "logstash": { "properties": { - "hash": { + "uuid": { "type": "keyword" }, - "id": { + "name": { + "type": "keyword" + }, + "ephemeral_id": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "http_address": { + "type": "keyword" + }, + "version": { + "type": "keyword" + }, + "snapshot": { + "type": "boolean" + }, + "status": { "type": "keyword" + }, + "pipeline": { + "properties": { + "workers": { + "type": "short" + }, + "batch_size": { + "type": "long" + } + } } } - } - } - }, - "logstash_stats": { - "properties": { + }, "events": { "properties": { - "duration_in_millis": { + "filtered": { "type": "long" }, "in": { @@ -628,11 +686,48 @@ }, "out": { "type": "long" + }, + "duration_in_millis": { + "type": "long" } } }, + "timestamp": { + "type": "date" + }, "jvm": { "properties": { + "uptime_in_millis": { + "type": "long" + }, + "gc": { + "properties": { + "collectors": { + "properties": { + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + }, + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + } + } + } + } + }, "mem": { "properties": { "heap_max_in_bytes": { @@ -640,30 +735,50 @@ }, "heap_used_in_bytes": { "type": "long" + }, + "heap_used_percent": { + "type": "long" } } - }, - "uptime_in_millis": { - "type": "long" - } - } - }, - "logstash": { - "properties": { - "uuid": { - "type": "keyword" - }, - "version": { - "type": "keyword" } } }, "os": { "properties": { + "cpu": { + "properties": { + "load_average": { + "properties": { + "1m": { + "type": "half_float" + }, + "5m": { + "type": "half_float" + }, + "15m": { + "type": "half_float" + } + } + } + } + }, "cgroup": { "properties": { + "cpuacct": { + "properties": { + "control_group": { + "type": "keyword" + }, + "usage_nanos": { + "type": "long" + } + } + }, "cpu": { "properties": { + "control_group": { + "type": "keyword" + }, "stat": { "properties": { "number_of_elapsed_periods": { @@ -678,70 +793,103 @@ } } } - }, - "cpuacct": { - "properties": { - "usage_nanos": { - "type": "long" - } - } } } - }, + } + } + }, + "process": { + "properties": { "cpu": { "properties": { - "load_average": { - "properties": { - "15m": { - "type": "half_float" - }, - "1m": { - "type": "half_float" - }, - "5m": { - "type": "half_float" - } - } + "percent": { + "type": "long" } } + }, + "max_file_descriptors": { + "type": "long" + }, + "open_file_descriptors": { + "type": "long" + } + } + }, + "reloads": { + "properties": { + "failures": { + "type": "long" + }, + "successes": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "events_count": { + "type": "long" + }, + "type": { + "type": "keyword" } } }, "pipelines": { + "type": "nested", "properties": { + "id": { + "type": "keyword" + }, + "hash": { + "type": "keyword" + }, + "ephemeral_id": { + "type": "keyword" + }, "events": { "properties": { - "duration_in_millis": { + "in": { + "type": "long" + }, + "filtered": { "type": "long" }, "out": { "type": "long" + }, + "duration_in_millis": { + "type": "long" + }, + "queue_push_duration_in_millis": { + "type": "long" } } }, - "hash": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, "queue": { "properties": { + "events_count": { + "type": "long" + }, + "type": { + "type": "keyword" + }, "max_queue_size_in_bytes": { "type": "long" }, "queue_size_in_bytes": { "type": "long" - }, - "type": { - "type": "keyword" } } }, "vertices": { + "type": "nested", "properties": { - "duration_in_millis": { - "type": "long" + "id": { + "type": "keyword" + }, + "pipeline_ephemeral_id": { + "type": "keyword" }, "events_in": { "type": "long" @@ -749,41 +897,109 @@ "events_out": { "type": "long" }, - "id": { - "type": "keyword" - }, - "pipeline_ephemeral_id": { - "type": "keyword" + "duration_in_millis": { + "type": "long" }, "queue_push_duration_in_millis": { - "type": "float" + "type": "long" + }, + "long_counters": { + "type": "nested", + "properties": { + "name": { + "type": "keyword" + }, + "value": { + "type": "long" + } + } + }, + "double_gauges": { + "type": "nested", + "properties": { + "name": { + "type": "keyword" + }, + "value": { + "type": "double" + } + } } - }, - "type": "nested" - } - }, - "type": "nested" - }, - "process": { - "properties": { - "cpu": { + } + }, + "reloads": { "properties": { - "percent": { + "failures": { + "type": "long" + }, + "successes": { "type": "long" } } } } }, - "queue": { + "workers": { + "type": "short" + }, + "batch_size": { + "type": "integer" + } + } + }, + "logstash_state": { + "properties": { + "uuid": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "http_address": { + "type": "keyword" + }, + "ephemeral_id": { + "type": "keyword" + }, + "version": { + "type": "keyword" + }, + "snapshot": { + "type": "boolean" + }, + "status": { + "type": "keyword" + }, + "pipeline": { "properties": { - "events_count": { - "type": "long" + "id": { + "type": "keyword" + }, + "hash": { + "type": "keyword" + }, + "ephemeral_id": { + "type": "keyword" + }, + "workers": { + "type": "short" + }, + "batch_size": { + "type": "integer" + }, + "format": { + "type": "keyword" + }, + "version": { + "type": "keyword" + }, + "representation": { + "enabled": false } } - }, - "timestamp": { - "type": "date" } } }, @@ -799,13 +1015,6 @@ "type": "text" } } - }, - "timestamp": { - "format": "date_time", - "type": "date" - }, - "type": { - "type": "keyword" } } }, @@ -1003,4 +1212,4 @@ } } } -} \ No newline at end of file +} diff --git a/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts index 45202e9c92d22..4877b0d5d8540 100644 --- a/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts +++ b/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts @@ -46,7 +46,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.click('savedObjectTitle'); }; - describe('visualize integration', () => { + // Failing: See https://github.com/elastic/kibana/issues/89958 + describe.skip('visualize integration', () => { before(async () => { await esArchiver.load('visualize'); await esArchiver.loadIfNeeded('logstash_functional'); diff --git a/yarn.lock b/yarn.lock index b5b166c87942d..f31122cc274c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1882,14 +1882,6 @@ pirates "^4.0.0" source-map-support "^0.5.16" -"@babel/runtime-corejs2@^7.2.0": - version "7.11.2" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.11.2.tgz#700a03945ebad0d31ba6690fc8a6bcc9040faa47" - integrity sha512-AC/ciV28adSSpEkBglONBWq4/Lvm6GAZuxIoyVtsnUpZMl0bxLtoChEnYAkP+47KyOCayZanojtflUEUJtR/6Q== - dependencies: - core-js "^2.6.5" - regenerator-runtime "^0.13.4" - "@babel/runtime-corejs3@^7.10.2": version "7.11.2" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz#02c3029743150188edeb66541195f54600278419" @@ -2146,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" @@ -9028,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" @@ -9048,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" @@ -11120,7 +11112,7 @@ core-js@^1.0.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.3, core-js@^2.6.5, core-js@^2.6.9: +core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.3, core-js@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== @@ -22199,10 +22191,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" @@ -30805,13 +30797,6 @@ xpath@0.0.27: resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== -xregexp@4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.2.4.tgz#02a4aea056d65a42632c02f0233eab8e4d7e57ed" - integrity sha512-sO0bYdYeJAJBcJA8g7MJJX7UrOZIfJPd8U2SC7B2Dd/J24U0aQNoGp33shCaBSWeb0rD5rh6VBUIXOkGal1TZA== - dependencies: - "@babel/runtime-corejs2" "^7.2.0" - "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"